70973a7a60ae0a499ceabc88bac8634f91433123 [canvas] Fix GPUCanvasContext external memory overflow
Triage note: Moves UpdateMemoryUsage() after Reshape and computes AllocatedBufferSize from actual swap_buffers_/resource_provider_. Web-reachable (WebGPU canvas) memory-accounting overflow; not a corruption primitive.
The bug at a glance
The overflow is in a memory-accounting figure, not in an allocation size, index, or pointer arithmetic that governs a buffer. GPUCanvasContext/OffscreenCanvas reported external buffer bytes to the memory-usage bookkeeping before the underlying shared images were actually allocated; the failing case reported bytes for an allocation that then failed downstream in WebGPUSwapBufferProvider, producing a wrong (overflowing) usage total. Consequences are limited to inaccurate memory pressure/accounting, not memory corruption. There is no attacker-controlled write and no OOB. Low/3.1 is generous already; this is closer to a correctness/telemetry fix with a latent-safety framing, hence watch-tier rather than a real vulnerability.
Titled an ’external memory overflow’ fix, which sounds heap-adjacent, but the diff shows it is an accounting-order bug: usage was updated at the wrong time (before allocation) and computed inaccurately (not derived from the real swap_buffers_ + resource_provider_ contents). The security-relevant nuance is only that a bogus large byte figure can perturb memory-pressure heuristics; there is no exploitable primitive in the diff.
Root cause
Two independent inaccuracies fed a bad byte total. (1) OffscreenCanvas::Reshape called UpdateMemoryUsage() early, before the context’s Reshape() had (re)allocated backing swap buffers, so the reported size did not correspond to allocated memory and could reflect a size that would fail to allocate. The patch removes the early call and instead calls UpdateMemoryUsage() right after context_->Reshape() for WebGL/WebGPU, matching HTMLCanvasElement ordering. (2) GPUCanvasContext previously lacked a real AllocatedBufferSize() override, so its contribution to memory accounting was not grounded in actually-held resources. The patch adds AllocatedBufferSize() summing resource_provider_->EstimatedSizeInBytes() and swap_buffers_->EstimatedSizeInBytes(), and adds the supporting EstimatedSizeInBytes() accessors on WebGPUSwapBufferProvider and SharedImagePool that iterate the real image pool. It also calls Host()->UpdateMemoryUsage() at the point a new swap texture is obtained. Using base::ByteSize (a checked byte quantity type) for these sums is what actually contains the overflow: the total is now built from real allocations rather than a speculative pre-allocation number.
Key code
Ground the reported size in actually-allocated buffers instead of a pre-allocation estimate
+base::ByteSize GPUCanvasContext::AllocatedBufferSize() const {
+ base::ByteSize result;
+ if (resource_provider_) {
+ result += resource_provider_->EstimatedSizeInBytes();
+ }
+ if (swap_buffers_) {
+ result += swap_buffers_->EstimatedSizeInBytes();
+ }
+ return result;
+}
Patch walkthrough
third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.cc— Moves UpdateMemoryUsage() out of the early size_ assignment and into the branch that runs after context_->Reshape() for WebGL/WebGPU. This ensures accounting reflects post-reshape allocation, aligning OffscreenCanvas with HTMLCanvasElement and preventing reporting of bytes that were never (or not yet) allocated.third_party/blink/renderer/modules/webgpu/gpu_canvas_context.cc / .h— Adds AllocatedBufferSize() computed from resource_provider_ + swap_buffers_, overrides AllocatedBufferCountPerPixel() as NOTREACHED() (not meaningful for this context), and calls Host()->UpdateMemoryUsage() when a new swap texture is created. Also drops a stray empty statement in GetSharedImageFormat().third_party/blink/renderer/platform/graphics/gpu/webgpu_swap_buffer_provider.cc / .h— Adds EstimatedSizeInBytes() summing the swap_buffer_pool_ estimate and the current_swap_buffer_’s shared image estimate, giving GPUCanvasContext a real figure to report.gpu/command_buffer/client/shared_image_pool.h— Adds EstimatedSizeInBytes() that iterates image_pool_ and sums each image’s EstimatedSizeInBytes() via base::ByteSize, providing the pool-level real allocation total.
Background
UpdateMemoryUsage / external memory — Blink reports canvas/GPU backing memory to V8 and internal memory-pressure accounting so GC and eviction heuristics see off-heap allocations. Over- or under-reporting skews these heuristics.
base::ByteSize — A typed byte quantity that centralizes byte arithmetic; using it for summation makes overflow behavior explicit and consistent versus raw size_t math scattered across callers.
WebGPUSwapBufferProvider / swap buffers — Manages the pool of shared images backing a WebGPU canvas’s swap chain. Actual allocation can fail (webgpu_swap_buffer_provider.cc:169), which is why reporting bytes before allocation was wrong.
SharedImagePool — Client-side pool of shared images; its true footprint is the sum of the images it currently holds.
Vulnerability window
- Introduced — OffscreenCanvas updated memory usage before context allocation, and GPUCanvasContext lacked a grounded AllocatedBufferSize override; long-standing accounting shape (bugs 470442752, 361124432).
- Observed — A test reported bytes before allocation and the allocation later failed, surfacing an overflow/inaccurate usage figure (b/470442752).
- Fixed — Landed 2026-01-07, refs/heads/main@{#1565581}.
Proof of concept
No test or exploit ships in this diff (the referenced failing test lives elsewhere). Behavioral recipe: create an OffscreenCanvas with a WebGPU context and reshape it to a very large dimension whose swap-buffer allocation will fail in WebGPUSwapBufferProvider; pre-patch the memory-usage accounting reflected the pre-allocation size, yielding an inflated/overflowing external-memory figure. No memory-safety exploit is possible from this; the observable effect is only a wrong accounting number and consequent perturbation of memory-pressure behavior. No exploit demonstrated.
Exploitation
- Reachability — Any page can drive OffscreenCanvas/GPUCanvasContext reshape with attacker-chosen dimensions, so the buggy code path is trivially reachable from script.
- Effect — Best case for an attacker is inflating or corrupting the reported external memory figure, which could nudge GC/eviction or telemetry. This is not a memory-corruption primitive.
- Ceiling — No OOB, no attacker-controlled allocation size derived from the overflow, no cross-origin effect. Impact is accounting-only; classifying beyond low would inflate it.
Detection & hunting
For defenders and detection engineers:
- Accounting before allocation — Flag UpdateMemoryUsage()/external-memory reporting calls that run before the corresponding allocation completes or that can report sizes for allocations that may fail.
- Raw byte summation — Look for footprint sums using raw size_t across pooled resources; prefer base::ByteSize or checked arithmetic to make overflow explicit.
- Missing AllocatedBufferSize overrides — Audit CanvasRenderingContext subclasses for AllocatedBufferSize/AllocatedBufferCountPerPixel implementations that do not reflect real held resources.
Audit directions
- Other canvas contexts — Compare WebGL, 2D, and ImageBitmap context memory reporting against this grounded pattern to ensure none report speculative pre-allocation sizes.
- SharedImage footprint accounting — Verify EstimatedSizeInBytes across shared-image producers cannot overflow base::ByteSize for legitimately large pools and that callers handle saturation.
- Memory-pressure consumers — Check whether any eviction/GC heuristic makes safety-relevant decisions on these figures, which would raise the severity of accounting bugs generally.