Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in WebGPU
DescriptionUse after free in WebGPU
ComponentWebGPU
Bug ClassUAF
Tracker491164019
Fix commit8974f6f5ba39 (dawn) +33/-16
CISA KEVNot listed
CreditedGoogle
Disclosed2026-03-23

Changed Functions

FunctionChangeNotes
if
src/dawn/wire/client/Buffer.cpp
modified
switch
src/dawn/wire/client/Buffer.cpp
modified

Files Changed

  • src/dawn/wire/client/Buffer.cpp
  • src/dawn/wire/client/EventManager.h
From 8974f6f5ba398442026895b7ef3be6fa07b2c22b Mon Sep 17 00:00:00 2001
From: Lokbondo Kung <lokokung@google.com>
Date: Fri, 13 Mar 2026 21:26:29 -0700
Subject: [PATCH] [dawn][wire][client] Prevent race condition in EventManager::SetFutureReady

The race only really happens for MapAsync because we can race between the
client aborting the map via Unmap or Destroy, and the server replying. As
a result, we can just put a lock around that particular area.

Bug: 491164019
Change-Id: Idb74cd07ed5ac94ab89522ffbea1f1b404bfc648
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/296655
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Kai Ninomiya <kainino@chromium.org>
Auto-Submit: Loko Kung <lokokung@google.com>
---

diff --git a/src/dawn/wire/client/Buffer.cpp b/src/dawn/wire/client/Buffer.cpp
index 84abc8a..f6be16b 100644
--- a/src/dawn/wire/client/Buffer.cpp
+++ b/src/dawn/wire/client/Buffer.cpp
@@ -88,8 +88,10 @@
                          uint64_t readDataUpdateInfoLength = 0,
                          const uint8_t* readDataUpdateInfo = nullptr) {
         if (status != WGPUMapAsyncStatus_Success) {
-            mStatus = status;
-            mMessage = ToString(message);
+            mResponse.Use([&](auto response) {
+                response->status = status;
+                response->message = ToString(message);
+            });
             return WireResult::Success;
         }
 
@@ -100,12 +102,14 @@
         }
 
         auto FailRequest = [this](const char* message) -> WireResult {
-            mStatus = static_cast<WGPUMapAsyncStatus>(0);
-            mMessage = message;
+            mResponse.Use([&](auto response) {
+                response->status = static_cast<WGPUMapAsyncStatus>(0);
+                response->message = message;
+            });
             return WireResult::FatalError;
         };
 
-        mStatus = status;
+        mResponse->status = status;
         const auto& pending = mBuffer->mPendingMapRequest.value();
         if (!pending.type) {
             return FailRequest("Invalid map call without a specified mapping type.");
@@ -140,31 +144,36 @@
 
   private:
     void CompleteImpl(FutureID futureID, EventCompletionType completionType) override {
+        // Move the response while holding the lock so that we avoid racing against the callback
+        // firing and the server replying with a response.
+        Response response = {};
+        mResponse.Use([&](auto res) { response = std::move(*res); });
+
         if (completionType == EventCompletionType::Shutdown) {
-            mStatus = WGPUMapAsyncStatus_CallbackCancelled;
-            mMessage = "A valid external Instance reference no longer exists.";
+            response.status = WGPUMapAsyncStatus_CallbackCancelled;
+            response.message = "A valid external Instance reference no longer exists.";
         }
 
-        auto Callback = [this]() {
+        auto Callback = [&]() {
             if (mCallback) {
-                mCallback(mStatus, ToOutputStringView(mMessage), mUserdata1.ExtractAsDangling(),
-                          mUserdata2.ExtractAsDangling());
+                mCallback(response.status, ToOutputStringView(response.message),
+                          mUserdata1.ExtractAsDangling(), mUserdata2.ExtractAsDangling());
             }
         };
 
         // The request has been cancelled before completion, return that result.
         if (!IsPendingRequest(futureID)) {
-            DAWN_ASSERT(mStatus != WGPUMapAsyncStatus_Success);
+            DAWN_ASSERT(response.status != WGPUMapAsyncStatus_Success);
             return Callback();
         }
 
         // Device destruction/loss implicitly makes the map requests aborted.
         if (!mBuffer->mDevice->IsAlive()) {
-            mStatus = WGPUMapAsyncStatus_Aborted;
-            mMessage = "The Device was lost before mapping was resolved.";
+            response.status = WGPUMapAsyncStatus_Aborted;
+            response.message = "The Device was lost before mapping was resolved.";
         }
 
-        if (mStatus == WGPUMapAsyncStatus_Success) {
+        if (response.status == WGPUMapAsyncStatus_Success) {
             DAWN_ASSERT(mBuffer->mPendingMapRequest && mBuffer->mPendingMapRequest->type);
             switch (*mBuffer->mPendingMapRequest->type) {
                 case MapRequestType::Read:
@@ -183,8 +192,14 @@
     raw_ptr<void> mUserdata1;
     raw_ptr<void> mUserdata2;
 
-    WGPUMapAsyncStatus mStatus;
-    std::string mMessage;
+    // The response for the map async callback needs to be protected with a lock since the response
+    // can be updated from the server (via a response) or from the client (via an unmap/destroy
+    // call).
+    struct Response {
+        WGPUMapAsyncStatus status;
+        std::string message;
+    };
+    MutexProtected<Response> mResponse;
 
     // Strong reference to the buffer so that when we call the callback we can pass the buffer.
     Ref<Buffer> mBuffer;
diff --git a/src/dawn/wire/client/EventManager.h b/src/dawn/wire/client/EventManager.h
index 16e442c..4d27f2b 100644
--- a/src/dawn/wire/client/EventManager.h
+++ b/src/dawn/wire/client/EventManager.h
@@ -76,6 +76,8 @@
     virtual EventType GetType() = 0;
 
     WGPUCallbackMode GetCallbackMode() const;
+
+    // Returns true iff the event is not |Pending|.
     bool IsReady() const;
 
     void SetReady();
Loading diff…

Original Bug Report

reported by 24...@project.gserviceaccount.com

Heap-use-after-free in blink::AsciiStringAttributes blink::CharacterAttributes<unsigned char>

Detailed Report: https://clusterfuzz.com/testcase?key=4605331497746432

Fuzzer: btiszka_webgpudomato Job Type: linux_asan_chrome_webgpu Platform Id: linux

Crash Type: Heap-use-after-free READ 16 Crash Address: 0x769ba8aa9980 Crash State: blink::AsciiStringAttributes blink::CharacterAttributes<unsigned char> blink::String::FromUTF8 blink::GPUBuffer::OnMapAsyncCallback

Sanitizer: address (ASAN)

Recommended Security Severity: Critical

Regressed: https://clusterfuzz.com/revisions?job=linux_asan_chrome_webgpu&range=1561910:1561922

Reproducer Testcase: https://clusterfuzz.com/download?testcase_id=4605331497746432

Issue filed automatically.

To reproduce this, please build the target in this report and run it against the reproducer testcase. Please use the GN arguments provided at bottom of this report when building the binary.

If you have trouble reproducing, please also export the environment variables listed under “[Environment]” in the crash stacktrace.

If you have any feedback on reproducing test cases, let us know at https://forms.gle/Yh3qCYFveHj6E5jz5 so we can improve.

View on issue tracker