Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in Dawn
DescriptionInsufficient validation of untrusted input in Dawn
ComponentDawn
Bug ClassLogic Error
Tracker511758373
Fix commit63321f623f72 (dawn) +22/-1
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
TEST_P
src/dawn/tests/white_box/SharedBufferMemoryTests.cpp
modified

Files Changed

  • src/dawn/native/Buffer.cpp
  • src/dawn/tests/white_box/SharedBufferMemoryTests.cpp
From 63321f623f725e52bfc92675581cba683a4b98bd Mon Sep 17 00:00:00 2001
From: Antonio Maiorano <amaiorano@google.com>
Date: Tue, 12 May 2026 15:11:27 -0700
Subject: [PATCH] [native][d3d12] SharedBufferMemory: Make Unmap after EndAccess fail

Calling Unamp on a SBM after EndAccess should fail. Otherwise, a
compromised renderer could take advantage of this to gain concurrent,
unbarriered access to the SBM.

Bug: 511758373
Change-Id: I3de036c0b71f05b2483fa5e0ae5ab7f543742197
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/307916
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Antonio Maiorano <amaiorano@google.com>
Reviewed-by: Loko Kung <lokokung@google.com>
---

diff --git a/src/dawn/native/Buffer.cpp b/src/dawn/native/Buffer.cpp
index 74d4848..0206566 100644
--- a/src/dawn/native/Buffer.cpp
+++ b/src/dawn/native/Buffer.cpp
@@ -885,7 +885,7 @@
         case BufferState::Unmapped:
             return {};
         case BufferState::SharedMemoryNoAccess:
-            break;
+            return DAWN_VALIDATION_ERROR("%s unmapped without shared memory access.", this);
         case BufferState::PendingMap:
         case BufferState::Destroyed:
             // UnmapInternal() already handled waiting for PendingMap to be done so there must have
diff --git a/src/dawn/tests/white_box/SharedBufferMemoryTests.cpp b/src/dawn/tests/white_box/SharedBufferMemoryTests.cpp
index b7ec223f5..0f4a7c3 100644
--- a/src/dawn/tests/white_box/SharedBufferMemoryTests.cpp
+++ b/src/dawn/tests/white_box/SharedBufferMemoryTests.cpp
@@ -368,6 +368,27 @@
     ASSERT_NE(state.fences[0], nullptr);
 }
 
+// Validate that calling Unmap after EndAccess is an error
+TEST_P(SharedBufferMemoryTests, UnmapAfterEndAccess) {
+    wgpu::SharedBufferMemory memory = GetParam().mBackend->CreateSharedBufferMemory(
+        device, wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst, kBufferSize);
+
+    // Buffer state is SharedMemoryNoAccess
+    wgpu::Buffer buffer = memory.CreateBuffer();
+
+    // BeginAccess transitions buffer state from SharedMemoryNoAccess to Unmapped
+    wgpu::SharedBufferMemoryBeginAccessDescriptor beginDesc = {};
+    beginDesc.initialized = true;
+    memory.BeginAccess(buffer, &beginDesc);
+
+    // EndAccess transitions buffer state back to SharedMemoryNoAccess
+    wgpu::SharedBufferMemoryEndAccessState endState = {};
+    memory.EndAccess(buffer, &endState);
+
+    // Unmapping the buffer now should be an error
+    ASSERT_DEVICE_ERROR(buffer.Unmap());
+}
+
 // Validate that calling BeginAccess twice produces an error.
 TEST_P(SharedBufferMemoryTests, EnsureNoDuplicateBeginAccessCalls) {
     wgpu::SharedBufferMemory memory =
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/src/dawn/tests/white_box/SharedBufferMemoryTests.cpp b/src/dawn/tests/white_box/SharedBufferMemoryTests.cpp
index b7ec223f5..0f4a7c3 100644
--- a/src/dawn/tests/white_box/SharedBufferMemoryTests.cpp
+++ b/src/dawn/tests/white_box/SharedBufferMemoryTests.cpp
@@ -368,6 +368,27 @@
     ASSERT_NE(state.fences[0], nullptr);
 }
 
+// Validate that calling Unmap after EndAccess is an error
+TEST_P(SharedBufferMemoryTests, UnmapAfterEndAccess) {
+    wgpu::SharedBufferMemory memory = GetParam().mBackend->CreateSharedBufferMemory(
+        device, wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst, kBufferSize);
+
+    // Buffer state is SharedMemoryNoAccess
+    wgpu::Buffer buffer = memory.CreateBuffer();
+
+    // BeginAccess transitions buffer state from SharedMemoryNoAccess to Unmapped
+    wgpu::SharedBufferMemoryBeginAccessDescriptor beginDesc = {};
+    beginDesc.initialized = true;
+    memory.BeginAccess(buffer, &beginDesc);
+
+    // EndAccess transitions buffer state back to SharedMemoryNoAccess
+    wgpu::SharedBufferMemoryEndAccessState endState = {};
+    memory.EndAccess(buffer, &endState);
+
+    // Unmapping the buffer now should be an error
+    ASSERT_DEVICE_ERROR(buffer.Unmap());
+}
+
 // Validate that calling BeginAccess twice produces an error.
 TEST_P(SharedBufferMemoryTests, EnsureNoDuplicateBeginAccessCalls) {
     wgpu::SharedBufferMemory memory =
Loading diff…

Original Bug Report

reported by vm...@google.com

State machine flaw in Dawn BufferBase::Unmap() leads to potential synchronization bypass

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 Chrome Security team. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A state machine flaw in Dawn’s BufferBase::Unmap allows a buffer in the SharedMemoryNoAccess state to incorrectly transition to the Unmapped state. This bypasses state validation and synchronization logic, potentially enabling a compromised renderer to perform unauthorized concurrent access to shared GPU resources.

Affected files:

  • third_party/dawn/src/dawn/native/Buffer.cpp
  • third_party/dawn/src/dawn/native/SharedResourceMemory.cpp
  • gpu/command_buffer/service/webgpu_decoder_impl.cc
  • gpu/command_buffer/service/shared_image/d3d_image_backing.cc
  • third_party/dawn/src/dawn/native/d3d12/BufferD3D12.cpp

Estimated timestamp from git blame: 2025-03-25

Summary

A state machine vulnerability exists in Dawn’s BufferBase::Unmap() implementation. When a buffer is in the SharedMemoryNoAccess state (typically after a Shared Image has been dissociated), the Unmap function incorrectly falls through a switch statement, resulting in an unconditional transition to the Unmapped state. This bypasses state validation and synchronization logic intended for shared resources. A compromised renderer could potentially exploit this to perform unauthorized, unbarriered concurrent access to shared GPU resources, leading to cross-origin information leaks or driver-level undefined behavior.

Vulnerability Details

In third_party/dawn/src/dawn/native/Buffer.cpp, the BufferBase::Unmap(bool forDestroy) function contains the following logic:

    switch (mState.load(std::memory_order::acquire)) {
        // ... other cases ...
        case BufferState::Unmapped:
            return {};
        case BufferState::SharedMemoryNoAccess:
            break;
        // ... error cases ...
    }

    mState.store(BufferState::Unmapped, std::memory_order::release);

When the state is SharedMemoryNoAccess, the break statement causes the execution to fall through to the unconditional mState.store(BufferState::Unmapped, ...) call at the end of the function.

Normally, the SharedMemoryNoAccess state indicates that the buffer’s access to shared memory has been terminated via EndAccess. Transitioning back to Unmapped (and subsequently to InUse) should only happen through BeginAccess, which enforces mutual exclusion and sets up necessary synchronization fences.

Potential Attack Scenario

A compromised renderer could potentially trigger this flaw through the following sequence of GPU command buffer operations:

  1. Associate Mailbox: The attacker issues an AssociateMailboxForBufferImmediate command to the WebGPU decoder with a shared image mailbox. The decoder injects the buffer into the Dawn wire server, which retains a strong reference to the buffer object.
  2. Dissociate Mailbox: The attacker sends DissociateMailboxForBuffer. This calls EndAccess on the shared resource, which sets the buffer’s state to SharedMemoryNoAccess, drains the pending synchronization fences from the buffer, and releases the mutual exclusion lock on the Shared Image.
  3. Bypass via Unmap: The attacker sends a BufferUnmap wire command using the server-side ID retained in Step 1. This invokes BufferBase::APIUnmap(), which hits the vulnerable fall-through in BufferBase::Unmap() and erroneously sets the buffer state to Unmapped.
  4. Concurrent Access: The attacker queues a GPU command (e.g., CopyBufferToBuffer) using the buffer. ValidateCanUseOnQueueNow() succeeds because the state is Unmapped (avoiding the validation error that SharedMemoryNoAccess would trigger).
  5. Synchronization Bypass: When Dawn prepares the command, SynchronizeBufferBeforeUseOnGPU() skips waiting on fences because they were already drained during Step 2.
  6. Meanwhile, the attacker (or another process) legitimately begins access to the same Shared Image. This results in concurrent, unbarriered access to the same underlying GPU resource from different queues, violating synchronization guarantees and potentially leaking stale GPU memory.

(Note: These are potential steps based on code analysis; our tooling agent does not yet have the capability to execute a live proof of concept.)

Suggested Fix

In third_party/dawn/src/dawn/native/Buffer.cpp within BufferBase::Unmap(), the case BufferState::SharedMemoryNoAccess: should return a validation error rather than breaking out of the switch statement. For example:

        case BufferState::SharedMemoryNoAccess:
            return DAWN_VALIDATION_ERROR("%s cannot be unmapped without shared memory access.", this);

Evaluated with Chrome root at commit: eca8648a4e1cdfdda68c495a6003059fed641955


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