CVE-2026-10921
Overview
Background
- Dawn
- Chromium’s native implementation of the WebGPU API, translating GPU commands from untrusted renderer processes into calls against platform graphics backends.
- `CommandAllocator`
- Dawn’s arena that serializes recorded GPU commands and their inline payloads (such as debug-marker strings) into contiguous memory for later replay.
- `StringView`
- a non-owning
{pointer, length}view over a caller-supplied string whoselength()is asize_tand may exceed the range of a 32-bit integer. - Integer truncation
- narrowing a wider value (here a
size_tlength) into a smaller type (uint32_t), silently discarding high-order bits and yielding a smaller stored length than the true byte count.
Root Cause Analysis
The helper AddNullTerminatedString copied a StringView into the CommandAllocator and reported its length back to the caller through a uint32_t* out-parameter, storing static_cast<uint32_t>(view.length()). Because std::string_view::length() returns a size_t, any string whose length exceeded UINT32_MAX (or, on the wraparound boundary, produced a smaller residue) was truncated, so the recorded length in InsertDebugMarkerCmd::length and PushDebugGroupCmd::length no longer matched the number of bytes actually written into the allocator. This violates the invariant that the stored command length equals the copied payload size, which downstream command processing relies on when reading the string back.
The fix changes the out-parameter, the two command struct fields, and the local computation to size_t, removing the narrowing cast so the full length is preserved end to end. With the length now stored at native pointer width, the recorded value always matches the bytes written and no truncation can occur.
size_t string length into a uint32_t when recording debug-marker commands, breaking the equality between the stored length and the actual payload size; widening the field and out-parameter to size_t eliminates the truncation entirely.Attack Path
- Craft an oversized label
A compromised or malicious renderer issues a WebGPU debug call (
insertDebugMarkerorpushDebugGroup) with a marker string whose length overflows 32 bits. - Trigger the truncating record
Dawn calls
AddNullTerminatedString, which copies the full string into theCommandAllocatorbut stores a truncateduint32_tlength into the command struct. - Create a length/payload mismatch
The recorded
lengthfield now under-reports (or otherwise mismatches) the bytes actually present in the allocator for that command. - Replay with the wrong length Later command iteration reads the string using the corrupted length, producing an out-of-bounds or otherwise inconsistent read/interpretation of allocator memory.
Impact Assessment
Files Changed
src/dawn/native/Commands.cppsrc/dawn/native/Commands.h
Audit Directions
- Length narrowing castsSearch for
static_cast<uint32_t>(or other narrowing) applied to.length(),.size(), orsize_tvalues before storing them in command structs or serialized buffers. - Mismatched length field widthsAudit command/record structures where a length field is declared
uint32_twhile the value written comes from asize_tsource, since the two can silently diverge. - StringView payload handlingReview every
CommandAllocatorpayload helper that copiesStringView/std::string_viewdata and reports a separate length, ensuring the reported length type matches the source width and is never truncated.
Patch
From a9185ea4ddcc586e2e0ed10fda490f5eb0c79561 Mon Sep 17 00:00:00 2001 From: Corentin Wallez <[email protected]> Date: Thu, 23 Apr 2026 10:46:32 -0700 Subject: [PATCH] [dawn][native] Use size_t to store string command lengths. Fixed: 499159695 Change-Id: Ia811965d2d9813eaf73341f9238e816663e5dd21 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/304695 Commit-Queue: Corentin Wallez <[email protected]> Reviewed-by: Loko Kung <[email protected]> --- diff --git a/src/dawn/native/Commands.cpp b/src/dawn/native/Commands.cpp index 2e538b1..05d9afa 100644 --- a/src/dawn/native/Commands.cpp +++ b/src/dawn/native/Commands.cpp @@ -435,9 +435,9 @@ } } -const char* AddNullTerminatedString(CommandAllocator* allocator, StringView s, uint32_t* length) { +const char* AddNullTerminatedString(CommandAllocator* allocator, StringView s, size_t* length) { std::string_view view = s; - *length = static_cast<uint32_t>(view.length()); + *length = view.length(); // Include extra null-terminator character. The string_view may not be null-terminated. It also // may already have a null-terminator inside of it, in which case adding the null-terminator is diff --git a/src/dawn/native/Commands.h b/src/dawn/native/Commands.h index c975fba..a0f9af0 100644 --- a/src/dawn/native/Commands.h +++ b/src/dawn/native/Commands.h @@ -342,7 +342,7 @@ }; struct InsertDebugMarkerCmd { - uint32_t length; + size_t length; }; struct PixelLocalStorageBarrierCmd {}; @@ -350,7 +350,7 @@ struct PopDebugGroupCmd {}; struct PushDebugGroupCmd { - uint32_t length; + size_t length; }; struct ResolveQuerySetCmd { @@ -465,7 +465,7 @@ void SkipCommand(CommandIterator* commands, Command type); // Helper function to copy a wgpu::StringView into a safely null-terminated C-string in commands. -const char* AddNullTerminatedString(CommandAllocator* allocator, StringView s, uint32_t* length); +const char* AddNullTerminatedString(CommandAllocator* allocator, StringView s, size_t* length); } // namespace dawn::native
Original Bug Report
GPU Process RCE via Integer Overflow in Dawn Command Iterator
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports without the security team.
Overview: An integer overflow exists in Dawn’s command processing when handling large debug marker strings. By supplying a string of length 0xFFFFFFFF, an attacker can desynchronize the command iterator. This causes the execution loop to interpret attacker-controlled string data as command IDs, triggering an unreachable code path that can be leveraged for Remote Code Execution.
Affected files:
third_party/dawn/src/dawn/native/Commands.cppthird_party/dawn/src/dawn/native/d3d12/CommandBufferD3D12.cppthird_party/dawn/src/dawn/native/vulkan/CommandBufferVk.cppthird_party/dawn/src/dawn/native/CommandAllocator.hthird_party/dawn/src/dawn/native/Commands.h
Estimated timestamp from git blame: 2025-11-17
Description
A potential integer overflow vulnerability exists in Dawn’s handling of debug marker and group wire messages (e.g., CommandEncoderInsertDebugMarker). This vulnerability can be triggered to corrupt the command stream iterator and execute attacker-controlled data as a function pointer jump, leading to potential Remote Code Execution (RCE) in the GPU process.
When a large string is provided to AddNullTerminatedString (in third_party/dawn/src/dawn/native/Commands.cpp), the string’s length is explicitly cast and stored in a 32-bit integer: cmd->length = static_cast<uint32_t>(view.length());. However, the memory allocation uses 64-bit arithmetic: allocator->AllocateData<char>(view.length() + 1);.
If an attacker supplies a string of exactly 0xFFFFFFFF (4GB) non-NUL bytes, the 4GB block is successfully allocated and populated. Later, when the backend command execution loops (e.g., in CommandBufferD3D12::RecordComputePass or FreeCommands) attempt to skip this data, they call:
mCommands.NextData<char>(cmd->length + 1);
Since cmd->length is a uint32_t (0xFFFFFFFF), the addition of 1 causes a 32-bit integer overflow, resulting in 0. Consequently, NextData<char>(0) is called. The command iterator consumes the internal kAdditionalData marker but advances its internal pointer by 0 bytes, failing to skip the 4GB string data.
On the next iteration, the command loop reads the next 4 bytes to determine the next Command ID. Because the pointer was not advanced, it reads directly from the attacker-controlled string data. The resulting invalid command ID falls through to the default: case in the execution loop’s switch statement, which calls DAWN_UNREACHABLE().
In standard Chromium release builds, DAWN_UNREACHABLE() expands to __builtin_unreachable(). Compilers (like Clang) use this as a hint to elide bounds checks on switch jump tables. As a result, the attacker-controlled command ID is used as an out-of-bounds index for the jump table, enabling control-flow hijacking.
Potential Attacker Steps
Note: Our tooling does not yet have the ability to run code, so these are theoretical steps an attacker might follow based on code analysis.
- Bypass Client Constraints: From a compromised renderer, manually construct Dawn chunked wire commands to bypass client-side buffer serializers, sending an
InsertDebugMarkercommand with a total size of0xFFFFFFFFbytes of non-NUL data. - Server Assembly: The GPU process’s
ChunkedCommandHandlerallocates a 4GB buffer (which succeeds on 64-bit systems with sufficient memory) and reconstructs the chunked payload. - Command Deserialization: The Dawn wire server deserializes the
WGPUStringView, preserving the0xFFFFFFFFlength (as it is bounded byWGPU_STRLEN, which isSIZE_MAX). - Trigger Overflow: The command is recorded into the native command buffer. The string is stored, but the
lengthfield in the command struct truncates. - Execution Desync: Upon submission, the GPU process attempts to execute the command buffer. The
cmd->length + 1overflow causes the iterator to stall exactly at the attacker’s payload. - Control Flow Hijack: The switch statement reads the attacker’s payload as a command ID, falling through to
DAWN_UNREACHABLE(). The compiler-optimized jump table reads an out-of-bounds function pointer dictated by the attacker’s fake ID, granting RCE.
Suggested Fix
- Validate String Lengths: Reject any
StringViewwith a length exceeding a reasonable upper bound (e.g., a few megabytes) early inNormalizeMessageStringor during Wire deserialization. - Use Checked Arithmetic: Modify
CommandAllocator::NextDataand iterator offset calculations to usebase::CheckedNumericto prevent overflow wrap-around. - Fix Struct Types: Change the
lengthfield in structures likeInsertDebugMarkerCmdandPushDebugGroupCmdtosize_trather thanuint32_t.
Evaluated with Chrome root at commit: ff3d2b74fa39431785bd60e51463b08fcc71ee33
Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.