HB Updated Aug 21, 2026

Rendering Code Outside the Sandbox: CVE-2026-76036 — Critical Dawn (WebGPU) Buffer Overflow in Chrome on Android

A single WebGPU texture-creation call carrying three innocent-looking properties — a depth/stencil format, a non-power-of-two size, and more than one mip level — is enough to trigger a heap buffer overflow inside Chrome’s GPU process on phones powered by Imagination PowerVR GPUs, Google’s Pixel 10 among them. NVD scores it 9.6 Critical and says plainly: arbitrary code execution outside the sandbox. The fix is a validation rule that resurrects a constraint older than the App Store.


Overview

On August 18, 2026, Google shipped Chrome 151.0.7922.169 to the stable channel — for Windows, Mac, Linux, and, in the same-day Android release, Google Play. The desktop release notes list 15 security fixes, and one of them is the kind of line item that makes a vulnerability researcher sit up straight:

[N/A][540087398] Critical CVE-2026-76036: Buffer overflow in Dawn. Reported by Google on 2026-07-28

Dawn is Chromium’s implementation of WebGPU, the modern graphics-and-compute API that replaced WebGL as the web’s window onto the GPU. A buffer overflow there is not another renderer bug — WebGPU work executes in Chrome’s GPU process, and the CVE’s own language reflects that: NVD’s record, published the same day, states the flaw “allowed a remote attacker to execute arbitrary code outside the sandbox via a crafted HTML page,” with a scope-changed CVSS 3.1 score of 9.6 — the highest NVD score of any fix in this release, and one of only two items Google itself labels Critical (the other being the WebGL overflow, CVE-2026-76034, at 8.8).

The cherry on top is the timing. Chrome for Android has long lagged desktop on WebGPU availability — caniuse’s Chrome for Android tracking shows default WebGPU support at this same major version (151) — so the API’s arrival as a default-on surface on Android coincides, almost to the week, with its first Critical-severity, Android-scoped CVE.

The bug’s public paper trail is unusually complete for a record whose details remain restricted. The fix landed in Dawn’s open-source repository on July 30, 2026 — two days after the internal report — and it tells the whole story in one commit message: “Disallow NPOT mipmapped depth/stencil textures on ImgTec. Can’t think of a way to work around this, so just disable it.” The root cause, in Dawn’s own words: a mip level miscomputation in the PowerVR proprietary Vulkan driver. In this article we reconstruct the vulnerability from the primary sources — the NVD/CVE records, the Chrome release notes, and the fix commit itself — walk through why a texture the size of 259×127 pixels can corrupt the GPU process heap, and release a detection harness you can run against your own devices.

Vulnerability Classification

Field Value
CVE ID CVE-2026-76036
CVSS v3.1 9.6 CRITICAL
CVSS Vector CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H
CWE CWE-122 — Heap-based Buffer Overflow
Affected Product Google Chrome on Android prior to 151.0.7922.169 (stable 151)
Vulnerable Component Dawn — Chromium’s WebGPU implementation, Vulkan backend path
Trigger Surface WebGPU GPUDevice.createTexture() — depth/stencil format + non-power-of-two dimensions + mipLevelCount > 1
Root Cause PowerVR proprietary Vulkan driver miscomputes mip-level sizes for NPOT depth/stencil textures (per Dawn fix commit)
Affected Hardware Devices exposing an Imagination (PowerVR) GPU through the proprietary Vulkan driver — the Pixel 10 (Tensor G5) is the reference device in Dawn’s own test expectations
Attack Vector Network — crafted HTML/HTTPS page invoking WebGPU; user interaction: one page visit
Impact Heap corruption → arbitrary code execution in the GPU process, i.e. outside the renderer sandbox (scope change)
Discoverer Google (internal report, Chromium bug 540087398)
CNA Chrome — chrome-cve-admin@google.com
Fixed Version 151.0.7922.169 (Android & Linux; .169/.170 Windows/Mac); superseded by 151.0.7922.173 on 2026-08-20
Fix Commit Dawn 178fb7048ad3 (2026-07-30) — toggle vulkan_disallow_npot_depth_stencil_mipmaps
Published Date 2026-08-18

Background: Dawn, WebGPU, and Why the GPU Process Counts as “Outside”

What Dawn Actually Is

Dawn is Google’s open-source implementation of WebGPU, and in Chromium it runs in two halves that never share an address space:

  • dawn::wire (the client) — the half that lives inside each renderer process. It is the navigator.gpu JavaScript binding’s backing: it speaks the WebGPU API but owns no GPU resources. Every call is serialized onto the Dawn wire — a command-buffer protocol — and shipped over Mojo IPC to the GPU process.
  • dawn::native (the server) — the half that lives in Chrome’s GPU (utility) process. It deserializes those commands, re-validates them against its own state, and translates them to the platform’s native graphics API. On Android, that native API is Vulkan, and it is Dawn’s Vulkan backend that hands work to the phone’s GPU vendor driver.

That split is Chrome’s site-isolation model applied to graphics. A page’s JavaScript never touches the GPU driver; it talks to a thin serialization library in a locked-down renderer, and the actual GPU work happens elsewhere, in a process that must hold real device access to function.

Why a GPU-Process Overflow “Escapes the Sandbox”

Chrome’s renderer processes are among the most confined processes on a modern phone: no file system to speak of, no device nodes, a syscall filter over them. The GPU process is also sandboxed, but by necessity it holds a privilege renderers do not — an open channel to the GPU device, because someone has to submit work to the Vulkan driver. From inside the renderer sandbox, the GPU process is the next room over, and the wall between them is exactly the boundary this CVE’s S:C (scope change) encoding describes.

So the exploitation chain implied by the NVD record reads: malicious page → WebGPU command → serialized over the wire → deserialized and executed by dawn::native in the GPU process → heap buffer overflow → attacker code running with the GPU process’s privileges, outside the renderer sandbox. From there, GPU-process compromise has historically been a staging point for attacks on kernel GPU drivers — a well-trodden escalation path on Android that Chrome’s own severity guidelines acknowledge by rating GPU-process bugs above renderer-only ones.

Chrome on Android process architecture: the sandboxed renderer running Dawn's wire client, the GPU process running Dawn native over Vulkan, and where CVE-2026-76036 fires

The WebGPU Attack Surface in One Call

WebGPU is reachable from any page in a secure context — HTTPS, in practice — with no permission prompt and no user gesture. Among the very first things any WebGPU program does is create textures:

const texture = device.createTexture({
    size:   { width: 259, height: 127 },
    format: 'depth24plus',          // a depth/stencil format
    mipLevelCount: 4,               // more than one mip level
    usage:  GPUTextureUsage.RENDER_ATTACHMENT,
});

createTexture is pure metadata from JavaScript’s point of view: dimensions, format, mip count, usage flags. All of that data flows to the GPU process, where Dawn validates it, computes the memory layout, and allocates. And as we will see, one particular combination of those three properties — format, size, mip count — is the entire attack surface of CVE-2026-76036.


The Vulnerability: Three Properties That Should Never Meet

Mip Levels and the Math That Must Not Diverge

A mip chain is a sequence of progressively smaller copies of a texture, each level half the previous one’s dimensions, floored, with a minimum of 1. For a power-of-two (POT) texture the arithmetic is clean — 256 halves into 128, 64, 32 … — every level’s footprint is exactly a quarter of the one before. For a non-power-of-two (NPOT) texture the chain still exists per the WebGPU spec (each dimension independently floors toward 1), but the byte layouts stop aligning on neat boundaries. A 259×127 depth texture walks down like this:

level 0:  259 x 127     level 3:   32 x 15
level 1:  129 x  63     level 4:   16 x  7
level 2:   64 x  31     level 5:    8 x  3   ... down to 1 x 1

Here is the crucial property of the system: the driver and the API implementation must agree on this arithmetic to the byte. Dawn sizes staging buffers, computes copy ranges, and lays out subresource offsets using the spec’s mip math. The Vulkan driver allocates and places the image’s memory using its own. If the two implementations of the same formula disagree — even by one row on one level — every copy operation between them becomes an out-of-bounds access: a write sized by one formula into an allocation sized by the other.

That is precisely the failure mode Dawn’s engineers recorded. The fix commit’s toggle description, verbatim from the repository:

“Reject NPOT depth/stencil textures with mipLevelCount > 1. Workaround for mip level miscomputation in PowerVR proprietary driver.”

And the backend gating comment, equally blunt:

“Driver bug miscomputes mip sizes for NPOT depth/stencil textures.”

NVD classifies the result as CWE-122, a heap-based buffer overflow, and Chrome’s release notes rate it Critical. The exact allocation that overflows sits in the restricted Chromium bug (Google withholds exploit-shaping details until most users have updated, standard policy for this severity class), but the public artifacts fix the entire perimeter: the trigger is a createTexture call, the combination is depth/stencil format + NPOT dimensions + multiple mip levels, the backend is Vulkan, the driver is PowerVR proprietary, and the consequence is heap corruption in the GPU process.

How the spec's mip arithmetic and the PowerVR driver's miscomputation diverge on a 259x127 depth texture, and why the mismatch runs past the end of the allocation

Why NPOT Depth Textures Specifically?

The combination is narrow, and the narrowness is a story in itself. Depth/stencil formats are the odd ones out in a GPU’s format table: they are not color data but coverage masks consumed by fixed-function depth/stencil testing, they frequently live in their own memory layouts, and they have a long history of special treatment. Non-power-of-two mipmapped depth textures were disallowed outright in the OpenGL ES 2.0 era — NPOT textures got no mipmaps at all until ES 3.0-class hardware made full NPOT support mandatory in 2013.

A driver bug that resurfaces a 2007-vintage constraint on a 2026 flagship GPU suggests a code path that was written for the POT world and never fully exercised — which is exactly the kind of latent path that a brand-new GPU in a brand-new device exposes for the first time at fleet scale.

The Hardware: Pixel 10 and the Imagination Transition

The fix does not apply everywhere — it is scoped to hardware that reports Imagination’s proprietary Vulkan driver. That points the finger squarely at the GPU family that arrived on Android’s flagship stage most recently: the Pixel 10’s Tensor G5, which marked Google’s move from Arm Mali to an Imagination Technologies (PowerVR-family) GPU in its 2025 hardware. Dawn’s own commit is the receipts — the fix was validated on the android-dawn-arm64-p10-rel builder (Pixel 10-class hardware running Dawn’s on-device test suite), and the commit’s WebGPU CTS expectations suppress the affected test categories on a device labeled android-pixel-10:

crbug.com/540087398 [ android-pixel-10 ] webgpu:api,validation,createTexture:mipLevelCount,format:dimension="*";format="depth*" [ Failure ]
crbug.com/540087398 [ android-pixel-10 ] webgpu:api,validation,createTexture:mipLevelCount,format:dimension="*";format="stencil*" [ Failure ]
crbug.com/540087398 [ android-pixel-10 ] webgpu:shader,execution,expression,call,builtin,texture_utils:createTextureWithRandomDataAndGetTexels_with_generator:format="depth*";viewDimension="*" [ Failure ]
crbug.com/540087398 [ android-pixel-10 ] webgpu:shader,execution,expression,call,builtin,texture_utils:createTextureWithRandomDataAndGetTexels_with_generator:format="stencil*";viewDimension="*" [ Failure ]

The NVD record scopes the CVE to “Google Chrome on Android” generally — that is how Chrome CNA records are phrased, matching the shipped browser version rather than enumerating SoCs. But the reachable population is devices whose WebGPU traffic ends in the PowerVR proprietary driver. Pixel 10 devices are the confirmed, named-in-source members of that class; other PowerVR-based Android hardware running the proprietary Vulkan driver is suspect by construction.


Tracing the Fix: One Toggle, Three Files

The patch, Dawn commit 178fb7048ad3 (landed July 30, 2026, 16:20 PDT — about 48 hours after the report), is a model of the “workaround first, driver fix later” discipline that GPU API implementations live by. Three changes carry the fix.

1. A new device toggle, documented with its root cause (src/dawn/native/Toggles.cpp):

{Toggle::VulkanDisallowNPOTDepthStencilMipmaps,
 {"vulkan_disallow_npot_depth_stencil_mipmaps",
  "Reject NPOT depth/stencil textures with mipLevelCount > 1. Workaround for mip level "
  "miscomputation in PowerVR proprietary driver.",
  "https://crbug.com/540087398", ToggleStage::Device}},

2. Validation that rejects the combination before it ever reaches the driver (src/dawn/native/Texture.cpp):

if (device->IsToggleEnabled(Toggle::VulkanDisallowNPOTDepthStencilMipmaps)) {
    DAWN_INVALID_IF(
        (format->aspects & (Aspect::Depth | Aspect::Stencil)) &&
            descriptor->mipLevelCount > 1 &&
            (!IsPowerOfTwo(descriptor->size.width) || !IsPowerOfTwo(descriptor->size.height)),
        "Non-power-of-two depth/stencil texture (%s) with mipLevelCount (%u) > 1 is "
        "disallowed on this device due to a driver bug.",
        descriptor->size, descriptor->mipLevelCount);
}

3. The toggle flips on only where the buggy driver lives (src/dawn/native/vulkan/PhysicalDeviceVk.cpp):

if (MayBeImaginationProprietary()) {
    // crbug.com/443906252 - Polyfill for case switch with large ranges.
    deviceToggles->Default(Toggle::VulkanPolyfillSwitchWithIf, true);

    // crbug.com/540087398 - Driver bug miscomputes mip sizes for NPOT depth/stencil textures.
    // TODO(https://crbug.com/540087398): Limit this to old drivers once there's a driver fix.
    deviceToggles->Default(Toggle::VulkanDisallowNPOTDepthStencilMipmaps, true);
}

Read together, the design decision is conservative in the right direction: rather than attempting to correct the driver’s mip arithmetic (whose exact divergence shape they evidently did not trust themselves to fully model), Dawn now refuses to create the texture at all on affected hardware. The JavaScript call that used to corrupt the heap now returns a validation error — "Non-power-of-two depth/stencil texture ... with mipLevelCount (...) > 1 is disallowed on this device due to a driver bug" — and the web page simply sees a failed texture creation, the same as any other invalid API use. Note also the TODO: the plan is to narrow the workaround to old driver versions once Imagination ships a driver-side fix, which tells you where Dawn’s engineers assign the underlying defect.

One more detail deserves a highlight. The commit also suppresses an existing end-to-end test, QueueWriteTextureTests.TextureWriteToMip, on ImgTec hardware — a test whose constants are a 259×127 texture written across its mip levels. That is almost certainly the shape of the original internal reproduction: Dawn’s hardware-in-the-loop test farm exercising NPOT mip writes on a Pixel 10 until the GPU process objected. Google’s own testing found this before anyone else could.


Exploitation Analysis

What an Attacker Needs

Requirement Detail
Vulnerable browser Chrome on Android < 151.0.7922.169, with WebGPU enabled (default in current Chrome for Android)
Vulnerable hardware GPU reached through Imagination’s proprietary Vulkan driver (Pixel 10-class confirmed by fix artifacts)
Victim interaction One visit to an attacker-controlled HTTPS page (UI:R — no gesture or permission prompt needed for createTexture)
Attacker privileges None (PR:N) — WebGPU texture creation is unauthenticated page content

The delivery story is the oldest one on the web: a link. WebGPU needs a secure context, which an attacker satisfies trivially with HTTPS. navigator.gpu is present by default; the harness in our PoC section needs no user gesture to start allocating textures. Complexity is rated Low (AC:L) — after the driver divergence, the remaining work for a motivated attacker is heap-grooming craft, not a race or an infoleak dependency.

What the Attacker Gets

The overflow corrupts the GPU process heap — the process that deserializes and executes WebGPU commands from every renderer. Successful exploitation means arbitrary code execution with GPU-process privileges: outside the renderer sandbox (S:C), with a live channel to the kernel’s GPU driver. Confidentiality/Integrity/Availability are all rated High because nothing about the corruption is contained to a tab: the GPU process is shared infrastructure. It is also worth saying what this is not: it is not a remote-code-execution-by-default outcome on arbitrary phones. The bug is hardware-gated, the exploitation is nontrivial, and the details needed to weaponize it are restricted. But “Critical, scope-changed, reachable from any page” is exactly the profile of a bug that in-the-wild attackers bundle into chains once details leak — which is why the update cadence matters.

End-to-end attack flow: crafted page, WebGPU createTexture, wire IPC, driver miscomputation, heap corruption in the GPU process

Timeline

Date Event
2026-07-28 Google files internal report — Chromium bug 540087398 (per Chrome release notes: “Reported by Google”)
2026-07-30 Fix lands in Dawn 178fb7048ad3: toggle, validation, ImgTec gating, test suppression; validated on android-dawn-arm64-p10-rel
2026-08-18 Chrome 151.0.7922.169 ships to stable — desktop and Android same day; release notes publish CVE-2026-76036 (Critical); NVD publishes the record (9.6)
2026-08-19 Android rollout post for .169 repeats the desktop parity rule for security fixes
2026-08-20 NVD record last updated; Chrome for Android 151.0.7922.173 ships as a follow-up stable update

Forty-eight hours from report to fix in the upstream library, nineteen days from fix to fleet rollout. The gap between those two numbers is where every unpatched device lives.


The Same Release, Four GPU Bugs

CVE-2026-76036 did not ship alone — the August 18 stable update contains a cluster of graphics-path memory-safety fixes worth understanding together, because they sketch the shape of Chrome’s current GPU attack surface:

CVE Component Bug class Score Chromium severity
CVE-2026-76036 Dawn (WebGPU) Heap buffer overflow 9.6 Critical
CVE-2026-76034 WebGL Buffer overflow 8.8 Critical
CVE-2026-76038 V8 Type confusion 8.8 High
CVE-2026-76047 V8 Type confusion 8.8 High
CVE-2026-76045 WebGL Use after free 8.8 High
CVE-2026-76046 ANGLE (Android-scoped) Buffer overflow 8.3 High
CVE-2026-76042 GPU Use of uninitialized resource High
CVE-2026-76041 Skia Information leak High

Two of those — the Dawn overflow and the ANGLE overflow (CVE-2026-76046, which NVD also scopes to Chrome on Android) — are sandbox-escape-class bugs by Chrome’s own severity labels, and the ANGLE bug even documents its precondition: “a remote attacker who had compromised the renderer process.” That is the standard two-stage anatomy of a browser exploit: a renderer bug (the V8 type confusions in this very release are candidates) plus a GPU-process bug to break out. CVE-2026-76036 is dangerous precisely because it is a self-contained first-and-second stage in one component — no renderer compromise required as a prerequisite.


Detection: Asking the Device Politely

Because the fix is a validation rule with a distinctive error message, you can determine — from JavaScript, on any patched Chrome — whether a device sits in the affected class. A patched Chrome on PowerVR hardware rejects the trigger combination with the “disallowed on this device due to a driver bug” message; on unaffected GPUs the same call succeeds or fails differently. That makes the detection problem trivial and, for defenders, makes post-patch triage a one-line check. Our PoC harness (next section) automates exactly this.

For defenders monitoring Android fleets, the pre-patch observable is uglier but distinctive: the GPU process dying while a page runs WebGPU. In adb logcat, a crashed GPU process shows up as a fatal signal in Chrome’s GPU process name, frequently with the vendor’s Vulkan ICD in the backtrace — look for Fatal signal lines against the Chrome GPU process shortly after a single page load. Correlate with the browser version (Settings → About Chrome, or adb shell dumpsys package com.android.chrome) against the 151.0.7922.169 floor.


Proof of Concept

https://github.com/Hunt-Benito/rendering-code-outside-the-sandbox-cve-2026-76036-dawn-webgpu-buffer-overflow-in-chrome-on-android

The exact trigger for the underlying overflow sits in a restricted bug, and we will not guess at heap layout. What we release instead is a differential detection harness built strictly from public artifacts: it enumerates the exact texture-creation matrix named by the fix commit (every depth/stencil format, NPOT dimensions including the 259×127 shape from Dawn’s suppressed test, mipLevelCount sweeps), runs each combination inside its own error scope, and classifies the outcome.

The Harness

$ python3 -m http.server 8000          # serve poc.html, open it on the device
                                     # at http://<host>:8000/poc.html — HTTPS or localhost

The core probe is deliberately boring — that is the point:

async function probe(device, format, width, height, mips) {
    device.pushErrorScope('validation');
    const t = device.createTexture({
        size: { width, height },
        format,
        mipLevelCount: mips,
        usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
    });
    const err = await device.popErrorScope();
    t?.destroy();
    return err ? err.message : null;   // null = combination accepted
}

Interpreting the matrix:

Observation Meaning
...disallowed on this device due to a driver bug Patched Chrome + PowerVR-class GPU — the device is in the CVE’s hardware class and the fix is active
Combination accepted, no error GPU outside the affected class (toggle off) — not reachable via this path
device.lost fires, page’s WebGPU dies, GPU-process crash in logcat on unpatched Chrome The pre-patch behavior this CVE exists to remove — update immediately
navigator.gpu undefined WebGPU unavailable; this attack surface is closed on that browser

Watching the GPU Process

The companion crash_monitor.py attaches over adb, pins the installed Chrome version, and filters logcat for the signatures that matter:

$ python3 crash_monitor.py
[*] com.android.chrome versionName=151.0.7922.83   <-- VULNERABLE (< .169)
[*] logcat: filtering for GPU-process fatal signals
[!] 08-21 14:02:11.482 F/<tid>(<pid>): Fatal signal 11 (SIGSEGV) ...
    process: com.android.chrome:sandboxed_process ... gpu

Attention! Run the harness only against devices and browsers you own or administer. On unpatched, affected hardware the pre-patch path is a genuine memory-corruption trigger, not a toy — the harness throttles its sweep precisely so a lab device survives long enough to report.


Remediation

  1. Update Chrome on Android to 151.0.7922.169 or later. Google Play rolls stable updates out over days; the About screen’s “Update” button pulls it immediately. The follow-up 151.0.7922.173 (2026-08-20) supersedes .169 — take whatever is newest. There is no configuration change to make and no policy to set: the fix is the Dawn validation rule itself, and it activates automatically on the affected hardware class.
  2. Prioritize PowerVR-equipped fleets. Pixel 10 and any device exposing Imagination’s proprietary Vulkan driver are the reachable population; they should move first, but the stable update is all-or-nothing by design — every Android Chrome user should take it regardless of GPU.
  3. Treat the whole August 18 release as a set. The same build carries the ANGLE sandbox-escape-class overflow (CVE-2026-76046) and two V8 type confusions; patch to the newest stable rather than cherry-picking milestones.
  4. For driver vendors and OEMs: the Dawn TODO explicitly plans to narrow the workaround “once there’s a driver fix” — the underlying mip-size miscomputation in the PowerVR proprietary Vulkan driver is the defect to fix at the source, and OEMs carrying that driver inherit the follow-through obligation.

The Bigger Picture: New Silicon, Old Wounds

Every GPU transition re-opens history. Non-power-of-two mipmapped textures were a solved problem — solved so thoroughly that “NPOT depth textures with mipmaps” sounds like a contradiction from another decade. Then a flagship phone line changed GPU vendors, a driver’s old arithmetic met a new fleet’s fuzzing infrastructure, and the web’s newest API became a delivery mechanism for the web’s oldest bug class.

Two things about this episode deserve the last words. The first is that the system worked: Google’s hardware-in-the-loop test farm caught a Critical, scope-changed memory bug on real silicon, upstream landed a mitigation in 48 hours, and the fleet fix shipped in under three weeks — all before any public exploitation. The second is how thin the margin is. The same release that ships this fix also ships four other GPU-component memory-safety bugs, one of them (ANGLE) explicitly documented as needing only a compromised renderer as its prerequisite. WebGPU has just become default-on surface on Android, which means every Android browser is now a front-end to a Vulkan driver written by whichever GPU vendor happened to win the phone’s design slot. The sandbox is only as strong as the arithmetic underneath it — and somewhere out there, right now, is the next driver rounding a mip level the wrong way.

We covered the adjacent half of this problem space in our earlier Android work — the TECNO Hi Browser path traversal and the llama.cpp JNI integer overflow — both cases where a trusted-format assumption met arithmetic that quietly disagreed with it. CVE-2026-76036 is the same species of failure, escalated to the GPU process: when two components must agree on layout math to the byte, any divergence is an exploit waiting for a page load.


SOURCES