← Chrome — 2026-W02

70973a7a60ae0a499ceabc88bac8634f91433123  [canvas] Fix GPUCanvasContext external memory overflow

severity low class IntegerOverflow CVSS 3.1 confidence 0.5 signal score 5.8 gpu/command_buffer/client AI review PLAUSIBLE
Primitive: GPUCanvasContext/OffscreenCanvas reported external buffer bytes before allocation, producing an inaccurate/overflowing memory-usage figure.
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

  1. Introduced — OffscreenCanvas updated memory usage before context allocation, and GPUCanvasContext lacked a grounded AllocatedBufferSize override; long-standing accounting shape (bugs 470442752, 361124432).
  2. Observed — A test reported bytes before allocation and the allocation later failed, surfacing an overflow/inaccurate usage figure (b/470442752).
  3. 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

  1. Reachability — Any page can drive OffscreenCanvas/GPUCanvasContext reshape with attacker-chosen dimensions, so the buggy code path is trivially reachable from script.
  2. 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.
  3. 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.

Before / after

Loading diff…