CVE-2026-4678
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifsrc/dawn/wire/client/Buffer.cpp |
modified | |
switchsrc/dawn/wire/client/Buffer.cpp |
modified |
Files Changed
src/dawn/wire/client/Buffer.cppsrc/dawn/wire/client/EventManager.h
Patch
From 8974f6f5ba398442026895b7ef3be6fa07b2c22b Mon Sep 17 00:00:00 2001 From: Lokbondo Kung <[email protected]> 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 <[email protected]> Reviewed-by: Corentin Wallez <[email protected]> Commit-Queue: Kai Ninomiya <[email protected]> Auto-Submit: Loko Kung <[email protected]> --- 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();
Original Bug Report
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.