
CVE-2026-5281 (Chrome Dawn WebGPU UAF) analysis, lab validation tools, and reproducible environment for vulnerable vs patched builds.
This vulnerability affected one of the clients we provide services to. This repository is our contribution to the original research: a centralized starting point for the group, so that if a similar vulnerability affecting this component appears again, we already have the groundwork in place. It brings together the theory behind the bug, a documented summary of the original researcher's findings, and a set of practical tools to verify exposure in a lab setting.
Note: I would have liked to share more of this research, but due to company restrictions I cannot disclose it further. Everything included here has been reviewed and does not violate any agreements I am subject to. The repository is therefore archived in its current state.
On April 1, 2026, Google released a Chrome security update addressing 21 vulnerabilities, one of which, CVE-2026-5281, was already being actively exploited in the wild at the time of disclosure. Three days later, CISA added it to the Known Exploited Vulnerabilities catalog and issued a binding operational directive requiring federal agencies to patch. By that point it had already affected us.
This repository exists for one reason: so that the next time something like this happens, we have a starting point instead of starting from scratch. It brings together:
When you want to understand why a vulnerability exists, you start with what the system was built to do and what assumptions it was designed around.
WebGPU exposes an API for performing operations, such as rendering and computation, on a Graphics Processing Unit. WebGPU is not an attempt to expose OpenGL or OpenGL ES (Embedded Systems). It is a new API that is built on the ideas of modern APIs such as Direct3D 12, Metal, and Vulkan.
WebGPU is the modern replacement for WebGL, the old GPU API that browsers have used for years. The key difference is that WebGPU was designed from the ground up for safety and explicit resource management. You declare the lifecycle of every buffer, texture, and pipeline yourself. The browser acts as a validation layer between your JavaScript and the GPU hardware.
The objects at the center of this vulnerability, in order of creation:
GPUAdapter ← represents a physical GPU or software fallback
└─ GPUDevice ← your logical connection to the adapter; owns everything
├─ GPUBuffer ← a chunk of GPU-accessible memory
├─ GPUShaderModule ← a compiled WGSL shader program
├─ GPUComputePipeline ← a shader wired to a pipeline layout
├─ GPUBindGroup ← binds buffers as inputs to a pipeline
├─ GPUCommandEncoder ← records a sequence of GPU commands
└─ GPUQueue ← submits recorded commands to hardware
The rule that matters here: every object is owned by the GPUDevice. Destroying a buffer while the device still has commands in flight that reference it is explicitly illegal under the spec. The Dawn implementation is supposed to detect and reject that. CVE-2026-5281 is a case where it did not.
Dawn - Open-Source WebGPU Implementation
Dawn is an open-source and cross-platform implementation of the work-in-progress WebGPU standard. It exposes a native C++ API that mirrors the WebGPU IDL with some extensions.
Dawn is the C++ library inside Chrome that translates WebGPU JavaScript calls into platform-native GPU commands. On Windows it targets D3D12, on macOS it targets Metal, and on Linux it targets Vulkan. It sits between Chrome's JavaScript engine and the hardware driver, and it is responsible for four things: validating API calls, serializing commands, tracking object lifetimes, and surfacing errors back to JavaScript.
CVE-2026-5281 lives in the lifetime tracking part. Specifically, in how long Dawn keeps GPU buffer objects alive while commands that reference them are still pending execution on the hardware queue.
JavaScript (V8)
│ WebGPU API calls
▼
Dawn (C++) - validates, serializes, tracks lifetimes, reports errors
│
▼
D3D12 (Windows) - Metal (macOS) - Vulkan (Linux)
│
▼
GPU hardware driver
│
▼
Physical GPU - shader cores, VRAM
You need a clear mental model of where things live in memory before a Use-After-Free makes intuitive sense.
High addresses
┌────────────────────────────────────┐
│ Kernel space │ The OS and drivers live here.
│ │ User-mode code cannot touch it.
├────────────────────────────────────┤
│ Stack │ Function call frames. Fast.
│ (grows downward) │ Freed automatically when the
│ │ function returns.
├────────────────────────────────────┤
│ Heap │ Dynamic allocations - malloc, new,
│ (grows upward) │ smart pointers like Ref<T>.
│ │ Freed only when you say so.
├────────────────────────────────────┤
│ BSS / Data / Text │ Globals, constants, compiled code.
└────────────────────────────────────┘
Low addresses
Dawn's C++ objects, like the internal object backing a GPUBuffer, live on the heap. They are reference-counted: a smart pointer keeps a count of how many things hold a reference to the object. When that count reaches zero, the destructor runs and the memory is returned to the allocator.
A GPUBuffer has two representations at the same time, one on the CPU side and one on the GPU:
CPU side (Dawn, system RAM)
└─ C++ object - metadata, state flags, and a hardware handle
│
│ handle: ID3D12Resource* (D3D12) - MTLBuffer (Metal) - VkBuffer (Vulkan)
▼
GPU side (driver, VRAM)
└─ Actual memory allocation on the graphics card
When JavaScript calls buffer.destroy(), the intended behavior is: mark the object as destroyed, decrement the reference count, release the hardware handle, and free the VRAM. The bug in CVE-2026-5281 causes the VRAM to be freed while the GPU command queue still holds a reference to that hardware handle, meaning the GPU is actively reading from or writing to memory that no longer belongs to it.
CWE-416: Use After Free (MITRE)
Referencing memory after it has been freed can cause a program to crash, use unexpected values, or execute code. The use of previously-freed memory can have any number of adverse consequences, ranging from the corruption of valid data to the execution of arbitrary code.
A Use-After-Free follows a fixed three-step pattern and is one of the most consistently exploited memory safety bug classes in browser security:
1. ALLOCATE - a heap object is created, and a pointer to it is stored somewhere
2. FREE - the object is destroyed and its memory is returned to the allocator
3. USE - the stale pointer is read or written after the memory was freed ← the bug
After step 2, the allocator can hand that same memory region to a completely different allocation. If an attacker can control what gets placed into that freed region, a technique called heap grooming, they can control what the stale pointer reads back. That is how a memory safety bug becomes code execution.
GPU-side UAF is harder to observe than CPU-side UAF, because:
The following is grounded entirely in what has been confirmed publicly.
Use-after-free in Dawn in Google Chrome prior to 146.0.7680.178 allowed a remote attacker who had compromised the renderer process to execute arbitrary code via a crafted HTML page.
The Hacker News: April 1, 2026
Google is aware that an exploit for CVE-2026-5281 exists in the wild.
Help Net Security: April 1, 2026
CVE-2026-5281 was flagged by a pseudonymous bug hunter (86ac1f1587b71893ed2ad792cd7dde32), who previously reported two vulnerabilities that have been fixed in the Chrome update released on March 23, 2026: a heap buffer overflow in WebGL (CVE-2026-4675) and another use-after-free bug in Dawn (CVE-2026-4676). The bug hunter also reported a third use-after-free in Dawn (CVE-2026-5284) that has been fixed this time around.
To understand where a UAF in Dawn can originate, it helps to see exactly how a WebGPU call travels from a line of JavaScript all the way to physical hardware:
JavaScript
↓ navigator.gpu → adapter → device → buffer / pipeline / encoder
↓ queue.submit([commandBuffer]) ← validation happens here
↓ buffer.destroy() ← if this races GPU execution, UAF
Dawn (C++) - validates API calls, serializes commands, tracks lifetimes
↓ translates WebGPU calls to platform-native API calls
D3D12 (Windows)
↓ ID3D12CommandQueue::ExecuteCommandLists()
↓ hardware handle for the buffer passed to the driver
GPU hardware
↓ shader cores execute the queued commands
↓ if the buffer was freed prematurely → they access freed VRAM ← UAF
The fundamental tension is that queue.submit() and buffer.destroy() are both JavaScript API calls that return immediately, but the GPU executes the submitted commands asynchronously, potentially long after both calls have returned. Dawn needs to keep buffer objects alive for the entire duration of GPU execution, not just until the JavaScript call returns.
When the UAF fires, the GPU hits what D3D12 calls a "Device Removed" event. The sequence is:
1. GPU shader accesses freed or reused VRAM
2. GPU memory protection triggers a hardware-level fault
3. D3D12 Timeout Detection and Recovery (TDR) kicks in
4. The driver signals DXGI_ERROR_DEVICE_REMOVED back to Chrome
5. Dawn's device-lost callback fires
6. Chrome surfaces GPUDeviceLostInfo to JavaScript
7. The DeviceLost promise resolves, the GPU context is gone
8. An uncapturederror event fires: "device lost due to internal error"
This is also what the automated test runner in this repository detects: it watches for those exact console signals to determine whether the vulnerability is triggerable in a given Chrome version.
The NVD description is specific about one important constraint: exploitation requires that the attacker has already compromised the renderer process. This means CVE-2026-5281 is not a standalone one-click RCE from a cold start, it is a sandbox escape that becomes part of a chain.
In practice, a full attack chain would look something like:
Initial access ← some other vulnerability gets code running in the renderer
↓
CVE-2026-5281 ← UAF in Dawn used to escape the renderer sandbox
↓
Arbitrary code ← execution in a higher-privilege Chrome process or OS context
This is exactly the exploitation model that makes browser GPU bugs high value: once you are in the renderer, Dawn is one of the natural next targets because it handles hardware-level memory with the kind of asynchronous complexity that produces these timing windows.
The confirmed impact at the time of disclosure was arbitrary code execution and the Vulners database notes data corruption and browser crashes as additional observed effects.
CVE-2026-5281 did not appear in isolation. It was the fourth Chrome zero-day of 2026, a year that was already on pace to exceed 2025's total count of eight zero-days before the end of Q1.
| Date | Event |
|---|
| February 2026 | CVE-2026-2441 patched, UAF in Chrome's CSS component, actively exploited |
| March 10, 2026 | CVE-2026-3909 and CVE-2026-3910 patched, both actively exploited zero-days |
| March 23, 2026 | CVE-2026-4675 (WebGL heap buffer overflow) and CVE-2026-4676 (UAF in Dawn) patched, same reporter as CVE-2026-5281 |
| April 1, 2026 | Google releases Chrome 146.0.7680.177/178, 21 vulnerabilities patched, CVE-2026-5281 confirmed exploited in the wild |
| April 1, 2026 | CISA adds CVE-2026-5281 to Known Exploited Vulnerabilities catalog |
| April 3, 2026 | Google acknowledges active exploitation against 3.5B Chrome users |
The same pseudonymous researcher who reported CVE-2026-5281 also reported three other vulnerabilities in the surrounding window (CVE-2026-4675, CVE-2026-4676, CVE-2026-5284, the last two also UAFs in Dawn). It suggests a focused, ongoing research effort specifically targeting Dawn's memory management.
The toolkit in this repository was built on top of original security research documenting the vulnerability's behavior in a lab setting. What follows is a summary of that research, the strategy used to trigger the UAF and the results observed.
The researcher's approach to triggering the UAF was designed to simultaneously satisfy the three conditions that make the race window reachable: enough GPU queue pressure to delay execution, tight enough timing between destroy and dispatch, and same-size buffer reallocation to maximize the chance of observable corruption.
The strategy breaks down into five steps:
Step 1 - Volume and pressure: 200 temporary WebGPU storage buffers allocated with randomized sizes (all multiples of 4 bytes as required by the WebGPU spec). This is not about filling VRAM, it is about creating enough pending work that the GPU cannot execute the commands immediately.
Step 2 - Compute thread saturation: 32 parallel compute pipelines queued with heavy workloads, inner loops running 1000 iterations and dispatch sizes of 4096 workgroups. The goal is to keep the GPU queue deeply backlogged so that the window between submit and execution stays open long enough to race.
Step 3 - The trap: Immediately after submitting all command buffers, destroy() is called on all 200 buffers. At this point the GPU has received the commands but has not yet executed them. Dawn has already passed its submit-time validation. The VRAM is freed.
Step 4 - The trigger: 32 new buffer allocations using the exact same sizes as the just-freed buffers. If the VRAM allocator returns the same physical addresses, which it often will since the sizes match, the GPU's pending commands now have a hardware handle pointing to memory that belongs to a different, live allocation.
Step 5 - Reuse commands submitted: Another round of command buffer submissions using the newly allocated buffers. At this point there are two sets of commands in the queue referencing what was once the same memory, with the GPU still working through the first set.
The result is a classic UAF at the VRAM layer: freed memory actively read by in-flight shader execution.
The researcher ran the PoC across both a vulnerable and a patched Chrome installation and observed a clean behavioral split:
Vulnerable run (Chrome < 146.0.7680.178):
[INFO] CVE-2026-5281 AGGRESSIVE PoC Loaded
[INFO] Initializing WebGPU context...
[INFO] WebGPU device initialized
[INFO] Starting aggressive UAF attacks...
[ERROR] UNCAUGHT GPU ERROR: device lost due to internal error
[CRASH] GPU DEVICE LOST: destroyed
[CRASH] [!!!] CRASH DETECTED! Check console for details.
The targeted Chrome process completely stopped rendering. The OS experienced a brief visual freeze, consistent with the display driver having to reset or halt processing after the GPU fault. The device lost event was mapped to a fatal GPU error rather than a standard WebGPU API validation error, which confirms the corrupted memory layout reached the hardware layer without being caught by Chrome's JavaScript-side sandboxing.
Patched run (Chrome >= 146.0.7680.178):
[INFO] CVE-2026-5281 AGGRESSIVE PoC Loaded
[INFO] Initializing WebGPU context...
[INFO] WebGPU device initialized
[INFO] Starting aggressive UAF attacks...
[INFO] Max attempts reached without crash
[INFO] Either browser is patched or target build not affected
No crash, no device loss, no fatal GPU signals across all attempts. The fix holds.
The following captures were taken during lab testing of the toolkit against both a vulnerable and a patched Chrome for Testing installation on a Windows machine with an Intel gen-12lp integrated GPU. Each tool was run against both targets to verify the behavioral split.
01 - Version Detector
| Vulnerable (< 146.0.7680.178) | Patched (>= 146.0.7680.178) |
|---|---|
![]() | ![]() |
02 - Vulnerability Checker
| Vulnerable | Patched |
|---|---|
![]() | ![]() |
03 - Local Scanner
| Vulnerable | Patched |
|---|---|
![]() | ![]() |
04 - Fleet Scanner
| Vulnerable | Patched |
|---|---|
![]() | ![]() |
05 - UAF Trigger
| Chrome | Firefox |
|---|---|
![]() | ![]() |
Firefox is included for comparison. Firefox uses its own WebGPU implementation and is not affected by this vulnerability. It completes all attempts without any crash signal regardless of version, which is the expected behavior.
06 - UAF Trigger + Automated Runner
| GPU Device Lost |
|---|
![]() |
Getting a visible crash signal is not always straightforward. Depending on the hardware and environment, the trigger may require some tuning to produce observable results. In our case, the behavior was reproducible, but not consistently exposed without adjusting the workload.
We successfully reproduced a Denial of Service against a vulnerable Chrome installation in a controlled lab environment. The UAF trigger causes the GPU to saturate to 100% utilization as the heavily backlogged command queue prevents the driver from servicing new memory management requests. During some runs, the GPU process entered an unrecoverable fault state, producing the following observable effects:
The GPU saturation and occasional exception confirm that the memory corruption is reaching the hardware layer: the freed buffer handle is accessed by in-flight shader execution, the GPU faults, and D3D12's TDR mechanism surfaces it as a device removal event. The patched version completed the same workload cleanly with no crash signal of any kind.
The DoS reproduction confirms the vulnerability. Ongoing work is focused on the binary-level analysis of the patch, specifically diffing Dawn's command buffer submission path between the last vulnerable build and 146.0.7680.178 to understand exactly where and how the reference counting fix was applied.
Regarding the automated runner: the original researcher published a runner script alongside their PoC. Our version required modifications to work reliably in a local lab context, specifically switching to Chrome's new headless mode and adding an option to allow GPU crash signals to propagate from the GPU process to the renderer. Without these two flags, Chrome silently absorbs GPU process crashes and the behavioral split between vulnerable and patched builds is not observable from JavaScript.
While the reverse engineering and binary diffing work is still in progress, the automated runner is not published in this repository. It will be included in a follow-up update once the patch analysis is complete.