Overview

High
Severity
8.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInteger overflow in Dawn
DescriptionInteger overflow in Dawn
ComponentDawn
Sub-componentDawn
Bug ClassInteger Overflow
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker499159695
Fix commita9185ea4ddcc (dawn) +5/-5
CWECWE-472, CWE-190 (Integer overflow)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-04-03 (per vendor advisory)
Fix landed2026-04-23 (public git)
Disclosed2026-06-02
Patch gap40 days — fix public in source before the advisory

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 whose length() is a size_t and may exceed the range of a 32-bit integer.
Integer truncation
narrowing a wider value (here a size_t length) 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.

Key insight
The single mistake was truncating a 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

  1. Craft an oversized label A compromised or malicious renderer issues a WebGPU debug call (insertDebugMarker or pushDebugGroup) with a marker string whose length overflows 32 bits.
  2. Trigger the truncating record Dawn calls AddNullTerminatedString, which copies the full string into the CommandAllocator but stores a truncated uint32_t length into the command struct.
  3. Create a length/payload mismatch The recorded length field now under-reports (or otherwise mismatches) the bytes actually present in the allocator for that command.
  4. 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

An attacker controlling a renderer or WebGPU-issuing process can drive an integer overflow that desynchronizes a recorded command length from its actual payload, leading to memory-unsafe access inside the GPU process where Dawn runs. Exploitation requires the ability to submit WebGPU commands with an attacker-controlled, extremely large debug-marker string, and success could yield information disclosure or memory corruption in the GPU process context.

Files Changed

  • src/dawn/native/Commands.cpp
  • src/dawn/native/Commands.h

Audit Directions

  • Length narrowing casts
    Search for static_cast<uint32_t> (or other narrowing) applied to .length(), .size(), or size_t values before storing them in command structs or serialized buffers.
  • Mismatched length field widths
    Audit command/record structures where a length field is declared uint32_t while the value written comes from a size_t source, since the two can silently diverge.
  • StringView payload handling
    Review every CommandAllocator payload helper that copies StringView/std::string_view data and reports a separate length, ensuring the reported length type matches the source width and is never truncated.
From a9185ea4ddcc586e2e0ed10fda490f5eb0c79561 Mon Sep 17 00:00:00 2001
From: Corentin Wallez <cwallez@chromium.org>
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 <cwallez@chromium.org>
Reviewed-by: Loko Kung <lokokung@google.com>
---

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
 
Loading diff…

Original Bug Report

reported by vm...@google.com

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.cpp
  • third_party/dawn/src/dawn/native/d3d12/CommandBufferD3D12.cpp
  • third_party/dawn/src/dawn/native/vulkan/CommandBufferVk.cpp
  • third_party/dawn/src/dawn/native/CommandAllocator.h
  • third_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.

  1. Bypass Client Constraints: From a compromised renderer, manually construct Dawn chunked wire commands to bypass client-side buffer serializers, sending an InsertDebugMarker command with a total size of 0xFFFFFFFF bytes of non-NUL data.
  2. Server Assembly: The GPU process’s ChunkedCommandHandler allocates a 4GB buffer (which succeeds on 64-bit systems with sufficient memory) and reconstructs the chunked payload.
  3. Command Deserialization: The Dawn wire server deserializes the WGPUStringView, preserving the 0xFFFFFFFF length (as it is bounded by WGPU_STRLEN, which is SIZE_MAX).
  4. Trigger Overflow: The command is recorded into the native command buffer. The string is stored, but the length field in the command struct truncates.
  5. Execution Desync: Upon submission, the GPU process attempts to execute the command buffer. The cmd->length + 1 overflow causes the iterator to stall exactly at the attacker’s payload.
  6. 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

  1. Validate String Lengths: Reject any StringView with a length exceeding a reasonable upper bound (e.g., a few megabytes) early in NormalizeMessageString or during Wire deserialization.
  2. Use Checked Arithmetic: Modify CommandAllocator::NextData and iterator offset calculations to use base::CheckedNumeric to prevent overflow wrap-around.
  3. Fix Struct Types: Change the length field in structures like InsertDebugMarkerCmd and PushDebugGroupCmd to size_t rather than uint32_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.

View on issue tracker