Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Dawn
DescriptionUse after free in Dawn
ComponentDawn
Bug ClassUAF
Tracker492139412
Fix commit4ba836a41006 (dawn) +251/-110
CISA KEVNot listed
Credited86ac1f1587b71893ed2ad792cd7dde32
Disclosed2026-03-31

Changed Functions

FunctionChangeNotes
if
src/dawn/tests/unittests/wire/WireBufferMappingTests.cpp
modified
TEST_P
src/dawn/tests/unittests/wire/WireBufferMappingTests.cpp
modified
TestCancelInCallback
src/dawn/tests/unittests/wire/WireBufferMappingTests.cpp
modified

Files Changed

  • include/dawn/wire/WireClient.h
  • src/dawn/tests/BUILD.gn
  • src/dawn/tests/unittests/wire/WireBufferMappingTests.cpp
From 4ba836a41006884c55731c72a1ba730d76cfb993 Mon Sep 17 00:00:00 2001
From: Corentin Wallez <[email protected]>
Date: Tue, 24 Mar 2026 07:55:36 -0700
Subject: [PATCH] Reland "[dawn][wire] Check that buffer is mapped in DeserializeDataUpdate."

This is a reland of commit e0a5e719c91ae3b60b3fc3d6d407b55e19337be4

Original change's description:
> [dawn][wire] Check that buffer is mapped in DeserializeDataUpdate.
>
> Previously the target of the WriteHandle for a buffer was set as soon as
> the buffer is mapped. Between the time it was first mapped and the time
> DeserializeDataUpdate was called (right before Unmap), the buffer could
> be implicitly unmapped by a call to Device::Destroy.
>
>  - Instead check for the buffer being mapped directly in
>    DeserializeDataUpdate, which remove the need to track a mapWriteState
>    on the ObjectData<WGPUBuffer>.
>  - Update the change detecting WireTests to account to GetMappedRange
>    being done in a different place now for writable buffers.
>  - Add a new test that allows injecting WireCmds directly for even more
>    precise but even more change detecting tests.
>  - Add necessary backdoors to WireClient and WireTest need for the new
>    tests.
>  - Link dawn::wire statically in dawn_unittests as we now need to use
>    some of its internals directly.
>
> Bug: 492139412
> Change-Id: Ibe9ab95ae7456c6629434d4978f439ebfe41c4d1
> Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/296817
> Reviewed-by: Loko Kung <[email protected]>
> Commit-Queue: Corentin Wallez <[email protected]>

Bug: 492139412
Change-Id: Ie60fe8d418299335fb2ec13d673be0a4776c32be
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/299215
Auto-Submit: Corentin Wallez <[email protected]>
Commit-Queue: Corentin Wallez <[email protected]>
Reviewed-by: Antonio Maiorano <[email protected]>
Commit-Queue: Antonio Maiorano <[email protected]>
---

diff --git a/include/dawn/wire/WireClient.h b/include/dawn/wire/WireClient.h
index 3b935ce..6472e40 100644
--- a/include/dawn/wire/WireClient.h
+++ b/include/dawn/wire/WireClient.h
@@ -96,6 +96,8 @@
     // Commands allocated after this point will not be sent.
     void Disconnect();
 
+    client::Client* GetImplForTesting();
+
   private:
     std::unique_ptr<client::Client> mImpl;
 };
diff --git a/src/dawn/tests/BUILD.gn b/src/dawn/tests/BUILD.gn
index 04900b6..ed0fc2e 100644
--- a/src/dawn/tests/BUILD.gn
+++ b/src/dawn/tests/BUILD.gn
@@ -468,6 +468,7 @@
     "unittests/wire/WireOptionalTests.cpp",
     "unittests/wire/WireQueueTests.cpp",
     "unittests/wire/WireShaderModuleTests.cpp",
+    "unittests/wire/WireSpecificCommandTests.cpp",
     "unittests/wire/WireTest.cpp",
     "unittests/wire/WireTest.h",
   ]
diff --git a/src/dawn/tests/unittests/wire/WireBufferMappingTests.cpp b/src/dawn/tests/unittests/wire/WireBufferMappingTests.cpp
index 0346239..5fbfa97 100644
--- a/src/dawn/tests/unittests/wire/WireBufferMappingTests.cpp
+++ b/src/dawn/tests/unittests/wire/WireBufferMappingTests.cpp
@@ -123,14 +123,14 @@
     }
 
     // Sets up the correct mapped range call expectations given the map mode.
-    void ExpectMappedRangeCall(uint64_t bufferSize, void* bufferContent) {
+    void ExpectMappedRangeCall() {
         wgpu::MapMode mapMode = GetMapMode();
         if (mapMode == wgpu::MapMode::Read) {
-            EXPECT_CALL(api, BufferGetConstMappedRange(apiBuffer, 0, bufferSize))
-                .WillOnce(Return(bufferContent));
+            EXPECT_CALL(api, BufferGetConstMappedRange(apiBuffer, 0, kBufferSize))
+                .WillOnce(Return(&mappedBufferContents));
         } else if (mapMode == wgpu::MapMode::Write) {
-            EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, bufferSize))
-                .WillOnce(Return(bufferContent));
+            EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, kBufferSize))
+                .WillOnce(Return(&mappedBufferContents));
         }
     }
 
@@ -144,14 +144,15 @@
         wgpu::MapMode mapMode = GetMapMode();
         MapAsync(mapMode, 0, kBufferSize);
 
-        uint32_t bufferContent = 31337;
         EXPECT_CALL(
             api, OnBufferMapAsync(apiBuffer, static_cast<WGPUMapMode>(mapMode), 0, kBufferSize, _))
             .WillOnce(InvokeWithoutArgs([&] {
                 api.CallBufferMapAsyncCallback(apiBuffer, WGPUMapAsyncStatus_Success,
                                                kEmptyOutputStringView);
             }));
-        ExpectMappedRangeCall(kBufferSize, &bufferContent);
+        if (mapMode & wgpu::MapMode::Read) {
+            ExpectMappedRangeCall();
+        }
         addExpectations();
 
         // The callback should get called with the expected status, regardless if the server has
@@ -230,14 +231,15 @@
         wgpu::MapMode mapMode = GetMapMode();
         MapAsync(mapMode, 0, kBufferSize);
 
-        uint32_t bufferContent = 31337;
         EXPECT_CALL(
             api, OnBufferMapAsync(apiBuffer, static_cast<WGPUMapMode>(mapMode), 0, kBufferSize, _))
             .WillOnce(InvokeWithoutArgs([&] {
                 api.CallBufferMapAsyncCallback(apiBuffer, WGPUMapAsyncStatus_Success,
                                                kEmptyOutputStringView);
             }));
-        ExpectMappedRangeCall(kBufferSize, &bufferContent);
+        if (mapMode & wgpu::MapMode::Read) {
+            ExpectMappedRangeCall();
+        }
 
         // Ensure that the server had a chance to respond if relevant.
         FlushClient();
@@ -260,7 +262,11 @@
         FlushCallbacks();
     }
 
+    // The buffer contents is in a member to ensure it outlives all test bodies (it is passed by
+    // pointer as the mocked result of GetMappedRange and can be derefenced anywhere in the test).
+    uint32_t mappedBufferContents = 31337;
     static constexpr uint64_t kBufferSize = sizeof(uint32_t);
+
     // A successfully created buffer
     wgpu::Buffer buffer;
     WGPUBuffer apiBuffer;
@@ -363,7 +369,12 @@
 // Test that the callback isn't fired twice when Unmap() is called inside the callback.
 TEST_P(WireBufferMappingTests, UnmapInsideMapCallback) {
     TestCancelInCallback([&]() { buffer.Unmap(); },
-                         [&]() { EXPECT_CALL(api, BufferUnmap(apiBuffer)); });
+                         [&]() {
+                             if (GetMapMode() & wgpu::MapMode::Write) {
+                                 ExpectMappedRangeCall();
+                             }
+                             EXPECT_CALL(api, BufferUnmap(apiBuffer));
+                         });
 }
 
 // Test that the callback isn't fired twice when Destroy() is called inside the callback.
@@ -394,14 +405,13 @@
 TEST_P(WireBufferMappingReadTests, MappingSuccess) {
     MapAsync(wgpu::MapMode::Read, 0, kBufferSize);
 
-    uint32_t bufferContent = 31337;
     EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Read, 0, kBufferSize, _))
         .WillOnce(InvokeWithoutArgs([&] {
             api.CallBufferMapAsyncCallback(apiBuffer, WGPUMapAsyncStatus_Success,
                                            kEmptyOutputStringView);
         }));
     EXPECT_CALL(api, BufferGetConstMappedRange(apiBuffer, 0, kBufferSize))
-        .WillOnce(Return(&bufferContent));
+        .WillOnce(Return(&mappedBufferContents));
 
     FlushClient();
     FlushFutures();
@@ -411,7 +421,7 @@
         FlushCallbacks();
     });
 
-    EXPECT_EQ(bufferContent,
+    EXPECT_EQ(mappedBufferContents,
               *static_cast<const uint32_t*>(buffer.GetConstMappedRange(0, kBufferSize)));
     EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1);
     buffer.Unmap();
@@ -424,14 +434,13 @@
     // Successful map
     MapAsync(wgpu::MapMode::Read, 0, kBufferSize);
 
-    uint32_t bufferContent = 31337;
     EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Read, 0, kBufferSize, _))
         .WillOnce(InvokeWithoutArgs([&] {
             api.CallBufferMapAsyncCallback(apiBuffer, WGPUMapAsyncStatus_Success,
                                            kEmptyOutputStringView);
         }));
     EXPECT_CALL(api, BufferGetConstMappedRange(apiBuffer, 0, kBufferSize))
-        .WillOnce(Return(&bufferContent));
+        .WillOnce(Return(&mappedBufferContents));
 
     FlushClient();
     FlushFutures();
@@ -459,7 +468,7 @@
         FlushCallbacks();
     });
 
-    EXPECT_EQ(bufferContent,
+    EXPECT_EQ(mappedBufferContents,
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/src/dawn/tests/BUILD.gn b/src/dawn/tests/BUILD.gn
index 04900b6..ed0fc2e 100644
--- a/src/dawn/tests/BUILD.gn
+++ b/src/dawn/tests/BUILD.gn
@@ -468,6 +468,7 @@
     "unittests/wire/WireOptionalTests.cpp",
     "unittests/wire/WireQueueTests.cpp",
     "unittests/wire/WireShaderModuleTests.cpp",
+    "unittests/wire/WireSpecificCommandTests.cpp",
     "unittests/wire/WireTest.cpp",
     "unittests/wire/WireTest.h",
   ]
diff --git a/src/dawn/tests/unittests/wire/WireBufferMappingTests.cpp b/src/dawn/tests/unittests/wire/WireBufferMappingTests.cpp
index 0346239..5fbfa97 100644
--- a/src/dawn/tests/unittests/wire/WireBufferMappingTests.cpp
+++ b/src/dawn/tests/unittests/wire/WireBufferMappingTests.cpp
@@ -123,14 +123,14 @@
     }
 
     // Sets up the correct mapped range call expectations given the map mode.
-    void ExpectMappedRangeCall(uint64_t bufferSize, void* bufferContent) {
+    void ExpectMappedRangeCall() {
         wgpu::MapMode mapMode = GetMapMode();
         if (mapMode == wgpu::MapMode::Read) {
-            EXPECT_CALL(api, BufferGetConstMappedRange(apiBuffer, 0, bufferSize))
-                .WillOnce(Return(bufferContent));
+            EXPECT_CALL(api, BufferGetConstMappedRange(apiBuffer, 0, kBufferSize))
+                .WillOnce(Return(&mappedBufferContents));
         } else if (mapMode == wgpu::MapMode::Write) {
-            EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, bufferSize))
-                .WillOnce(Return(bufferContent));
+            EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, kBufferSize))
+                .WillOnce(Return(&mappedBufferContents));
         }
     }
 
@@ -144,14 +144,15 @@
         wgpu::MapMode mapMode = GetMapMode();
         MapAsync(mapMode, 0, kBufferSize);
 
-        uint32_t bufferContent = 31337;
         EXPECT_CALL(
             api, OnBufferMapAsync(apiBuffer, static_cast<WGPUMapMode>(mapMode), 0, kBufferSize, _))
             .WillOnce(InvokeWithoutArgs([&] {
                 api.CallBufferMapAsyncCallback(apiBuffer, WGPUMapAsyncStatus_Success,
                                                kEmptyOutputStringView);
             }));
-        ExpectMappedRangeCall(kBufferSize, &bufferContent);
+        if (mapMode & wgpu::MapMode::Read) {
+            ExpectMappedRangeCall();
+        }
         addExpectations();
 
         // The callback should get called with the expected status, regardless if the server has
@@ -230,14 +231,15 @@
         wgpu::MapMode mapMode = GetMapMode();
         MapAsync(mapMode, 0, kBufferSize);
 
-        uint32_t bufferContent = 31337;
         EXPECT_CALL(
             api, OnBufferMapAsync(apiBuffer, static_cast<WGPUMapMode>(mapMode), 0, kBufferSize, _))
             .WillOnce(InvokeWithoutArgs([&] {
                 api.CallBufferMapAsyncCallback(apiBuffer, WGPUMapAsyncStatus_Success,
                                                kEmptyOutputStringView);
             }));
-        ExpectMappedRangeCall(kBufferSize, &bufferContent);
+        if (mapMode & wgpu::MapMode::Read) {
+            ExpectMappedRangeCall();
+        }
 
         // Ensure that the server had a chance to respond if relevant.
         FlushClient();
@@ -260,7 +262,11 @@
         FlushCallbacks();
     }
 
+    // The buffer contents is in a member to ensure it outlives all test bodies (it is passed by
+    // pointer as the mocked result of GetMappedRange and can be derefenced anywhere in the test).
+    uint32_t mappedBufferContents = 31337;
     static constexpr uint64_t kBufferSize = sizeof(uint32_t);
+
     // A successfully created buffer
     wgpu::Buffer buffer;
     WGPUBuffer apiBuffer;
@@ -363,7 +369,12 @@
 // Test that the callback isn't fired twice when Unmap() is called inside the callback.
 TEST_P(WireBufferMappingTests, UnmapInsideMapCallback) {
     TestCancelInCallback([&]() { buffer.Unmap(); },
-                         [&]() { EXPECT_CALL(api, BufferUnmap(apiBuffer)); });
+                         [&]() {
+                             if (GetMapMode() & wgpu::MapMode::Write) {
+                                 ExpectMappedRangeCall();
+                             }
+                             EXPECT_CALL(api, BufferUnmap(apiBuffer));
+                         });
 }
 
 // Test that the callback isn't fired twice when Destroy() is called inside the callback.
@@ -394,14 +405,13 @@
 TEST_P(WireBufferMappingReadTests, MappingSuccess) {
     MapAsync(wgpu::MapMode::Read, 0, kBufferSize);
 
-    uint32_t bufferContent = 31337;
     EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Read, 0, kBufferSize, _))
         .WillOnce(InvokeWithoutArgs([&] {
             api.CallBufferMapAsyncCallback(apiBuffer, WGPUMapAsyncStatus_Success,
                                            kEmptyOutputStringView);
         }));
     EXPECT_CALL(api, BufferGetConstMappedRange(apiBuffer, 0, kBufferSize))
-        .WillOnce(Return(&bufferContent));
+        .WillOnce(Return(&mappedBufferContents));
 
     FlushClient();
     FlushFutures();
@@ -411,7 +421,7 @@
         FlushCallbacks();
     });
 
-    EXPECT_EQ(bufferContent,
+    EXPECT_EQ(mappedBufferContents,
               *static_cast<const uint32_t*>(buffer.GetConstMappedRange(0, kBufferSize)));
     EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1);
     buffer.Unmap();
@@ -424,14 +434,13 @@
     // Successful map
     MapAsync(wgpu::MapMode::Read, 0, kBufferSize);
 
-    uint32_t bufferContent = 31337;
     EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Read, 0, kBufferSize, _))
         .WillOnce(InvokeWithoutArgs([&] {
             api.CallBufferMapAsyncCallback(apiBuffer, WGPUMapAsyncStatus_Success,
                                            kEmptyOutputStringView);
         }));
     EXPECT_CALL(api, BufferGetConstMappedRange(apiBuffer, 0, kBufferSize))
-        .WillOnce(Return(&bufferContent));
+        .WillOnce(Return(&mappedBufferContents));
 
     FlushClient();
     FlushFutures();
@@ -459,7 +468,7 @@
         FlushCallbacks();
     });
 
-    EXPECT_EQ(bufferContent,
+    EXPECT_EQ(mappedBufferContents,
               *static_cast<const uint32_t*>(buffer.GetConstMappedRange(0, kBufferSize)));
 }
 
@@ -478,7 +487,6 @@
 TEST_P(WireBufferMappingWriteTests, MappingSuccess) {
     MapAsync(wgpu::MapMode::Write, 0, kBufferSize);
 
-    uint32_t serverBufferContent = 31337;
     uint32_t updatedContent = 4242;
 
     EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Write, 0, kBufferSize, _))
@@ -486,8 +494,6 @@
             api.CallBufferMapAsyncCallback(apiBuffer, WGPUMapAsyncStatus_Success,
                                            kEmptyOutputStringView);
         }));
-    EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, kBufferSize))
-        .WillOnce(Return(&serverBufferContent));
 
     // The map write callback always gets a buffer full of zeroes.
     FlushClient();
@@ -504,13 +510,15 @@
     // Write something to the mapped pointer
     *lastMapWritePointer = updatedContent;
 
+    EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, kBufferSize))
+        .WillOnce(Return(&mappedBufferContents));
     EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1);
     buffer.Unmap();
 
     FlushClient();
 
     // After the buffer is unmapped, the content of the buffer is updated on the server
-    ASSERT_EQ(serverBufferContent, updatedContent);
+    ASSERT_EQ(mappedBufferContents, updatedContent);
 }
 
 // Check that an error map write while a buffer is already mapped.
@@ -518,14 +526,11 @@
     // Successful map
     MapAsync(wgpu::MapMode::Write, 0, kBufferSize);
 
-    uint32_t bufferContent = 31337;
     EXPECT_CALL(api, OnBufferMapAsync(apiBuffer, WGPUMapMode_Write, 0, kBufferSize, _))
         .WillOnce(InvokeWithoutArgs([&] {
             api.CallBufferMapAsyncCallback(apiBuffer, WGPUMapAsyncStatus_Success,
                                            kEmptyOutputStringView);
         }));
-    EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, kBufferSize))
-        .WillOnce(Return(&bufferContent));
 
     FlushClient();
     FlushFutures();
@@ -574,11 +579,10 @@
 
     uint32_t apiBufferData = 1234;
     EXPECT_CALL(api, DeviceCreateBuffer(apiDevice, _)).WillOnce(Return(apiBuffer));
-    EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, 4)).WillOnce(Return(&apiBufferData));
-
     buffer = device.CreateBuffer(&descriptor);
     FlushClient();
 
+    EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, 4)).WillOnce(Return(&apiBufferData));
     EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1);
     buffer.Unmap();
     FlushClient();
@@ -590,10 +594,7 @@
     descriptor.size = kBufferSize;
     descriptor.mappedAtCreation = true;
 
-    uint32_t apiBufferData = 1234;
     EXPECT_CALL(api, DeviceCreateBuffer(apiDevice, _)).WillOnce(Return(apiBuffer));
-    EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, 4)).WillOnce(Return(&apiBufferData));
-
     buffer = device.CreateBuffer(&descriptor);
     FlushClient();
 
@@ -611,11 +612,10 @@
 
     uint32_t apiBufferData = 1234;
     EXPECT_CALL(api, DeviceCreateBuffer(apiDevice, _)).WillOnce(Return(apiBuffer));
-    EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, 4)).WillOnce(Return(&apiBufferData));
-
     buffer = device.CreateBuffer(&descriptor);
     FlushClient();
 
+    EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, 4)).WillOnce(Return(&apiBufferData));
     EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1);
     buffer.Unmap();
     FlushClient();
@@ -627,8 +627,6 @@
             api.CallBufferMapAsyncCallback(apiBuffer, WGPUMapAsyncStatus_Success,
                                            kEmptyOutputStringView);
         }));
-    EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, kBufferSize))
-        .WillOnce(Return(&apiBufferData));
     FlushClient();
     FlushFutures();
 
@@ -647,8 +645,6 @@
 
     uint32_t apiBufferData = 1234;
     EXPECT_CALL(api, DeviceCreateBuffer(apiDevice, _)).WillOnce(Return(apiBuffer));
-    EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, 4)).WillOnce(Return(&apiBufferData));
-
     buffer = device.CreateBuffer(&descriptor);
     FlushClient();
 
@@ -673,6 +669,7 @@
 
     EXPECT_NE(nullptr, static_cast<const uint32_t*>(buffer.GetConstMappedRange(0, kBufferSize)));
 
+    EXPECT_CALL(api, BufferGetMappedRange(apiBuffer, 0, 4)).WillOnce(Return(&apiBufferData));
     EXPECT_CALL(api, BufferUnmap(apiBuffer)).Times(1);
     buffer.Unmap();
 
@@ -767,14 +764,15 @@
     wgpu::MapMode mapMode = GetMapMode();
     MapAsync(mapMode, 0, kBufferSize);
 
-    uint32_t bufferContent = 0;
     EXPECT_CALL(api,
                 OnBufferMapAsync(apiBuffer, static_cast<WGPUMapMode>(mapMode), 0, kBufferSize, _))
         .WillOnce(InvokeWithoutArgs([&] {
             api.CallBufferMapAsyncCallback(apiBuffer, WGPUMapAsyncStatus_Success,
                                            kEmptyOutputStringView);
         }));
-    ExpectMappedRangeCall(kBufferSize, &bufferContent);
+    if (mapMode & wgpu::MapMode::Read) {
+        ExpectMappedRangeCall();
+    }
 
     FlushClient();
     ExpectWireCallbacksWhen([&](auto& mockCb) {
@@ -803,14 +801,15 @@
     MapAsync(mapMode, 0, kBufferSize);
 
     // Calls for the first successful map.
-    uint32_t bufferContent = 0;
     EXPECT_CALL(api,
                 OnBufferMapAsync(apiBuffer, static_cast<WGPUMapMode>(mapMode), 0, kBufferSize, _))
         .WillOnce(InvokeWithoutArgs([&] {
             api.CallBufferMapAsyncCallback(apiBuffer, WGPUMapAsyncStatus_Success,
                                            kEmptyOutputStringView);
         }));
-    ExpectMappedRangeCall(kBufferSize, &bufferContent);
+    if (mapMode & wgpu::MapMode::Read) {
+        ExpectMappedRangeCall();
+    }
 
     if (IsSpontaneous()) {
         // In spontaneous mode, the second map on the pending immediately calls the callback.
@@ -849,7 +848,6 @@
 // Test that GetMapState() returns map state as expected
 TEST_P(WireBufferMappingTests, GetMapState) {
     wgpu::MapMode mapMode = GetMapMode();
... (truncated)
Loading diff…

Original Bug Report

reported by [email protected]

UAF write in Dawn wire server: BufferUpdateMappedData writes to freed GPU memory after DeviceDestroy

Summary

A use-after-free write exists in the Dawn wire server running in the GPU process. When a compromised renderer sends a DeviceDestroy wire command followed by BufferUpdateMappedData for a buffer that was created with mappedAtCreation, the server performs a memcpy into GPU buffer memory that has already been freed during device destruction. The dangling pointer is a raw uint8_t* stored in the wire server’s WriteHandle, which is not protected by raw_ptr, MiraclePtr, or any other mitigation. The attacker controls the write content (sourced from shared memory), offset, and size. This affects all platforms with WebGPU support.

Bisect

Introducing Commit: f93fa6acd96d77df9ef48f8f5ff78ea72cebf5ae (Dawn)

This commit implemented mappedAtCreation in the Dawn wire server by calling writeHandle->SetTarget(mapping, descriptor->size) inside DoDeviceCreateBuffer. The raw pointer stored via SetTarget was never invalidated on the DeviceDestroy path, creating the dangling pointer condition. A later commit (6e680fc56f, 2021-07-08) extended WriteHandle lifetime to persist across map/unmap cycles, widening the exploitable window, but the root gap has existed since this original commit.

Root Cause

Chromium’s WebGPU implementation uses the Dawn wire protocol to relay GPU commands from the renderer process to the GPU process. When a renderer creates a buffer with mappedAtCreation=true, the wire server obtains a pointer to the native mapped memory and stores it in a WriteHandle via SetTarget:

// third_party/dawn/src/dawn/wire/server/ServerBuffer.cpp
writeHandle->SetTarget(mapping);
buffer->mapWriteState = BufferMapWriteState::Mapped;

SetTarget stores this as a bare uint8_t*:

// third_party/dawn/src/dawn/wire/WireServer.cpp
void MemoryTransferService::WriteHandle::SetTarget(void* data) {
    mTargetData = static_cast<uint8_t*>(data);
}

The field declaration confirms it is not wrapped in raw_ptr or any other safety abstraction:

// third_party/dawn/include/dawn/wire/WireServer.h
uint8_t* mTargetData = nullptr;
size_t mDataLength = 0;

When the renderer later sends a DeviceDestroy command, the wire server delegates directly to the native layer without cleaning up any per-buffer state:

// (auto-generated) third_party/dawn/src/dawn/wire/server/ServerDoers_autogen.cpp
WireResult Server::DoDeviceDestroy(WGPUDevice self) {
    mProcs->deviceDestroy(self);
    return WireResult::Success;
}

The native layer destroys all device-owned objects, including buffers. On Vulkan, this frees the underlying memory through the FencedDeleter:

// third_party/dawn/src/dawn/native/vulkan/BufferVk.cpp
void Buffer::DestroyImpl(DestroyReason reason) {
    BufferBase::DestroyImpl(reason);
    ToBackend(GetDevice())->GetResourceMemoryAllocator()->Deallocate(&mMemoryAllocation);
    if (mHandle != VK_NULL_HANDLE) {
        ToBackend(GetDevice())->GetFencedDeleter()->DeleteWhenUnused(mHandle);
        mHandle = VK_NULL_HANDLE;
    }
}

The FencedDeleter flushes all pending deletions during device teardown, calling vkFreeMemory which ultimately calls free() on the underlying heap allocation (in SwiftShader’s case via sw::freeMemory).

After the memory is freed, the wire server’s buffer->writeHandle still holds the stale mTargetData pointer, and buffer->mapWriteState is still BufferMapWriteState::Mapped. A subsequent BufferUpdateMappedData command passes all validation checks and reaches the memcpy:

// gpu/command_buffer/service/dawn_service_memory_transfer_service.cc
bool WriteHandleImpl::DeserializeDataUpdate(const void* deserialize_pointer,
                                            size_t deserialize_size,
                                            size_t offset,
                                            size_t size) {
    // ...bounds checks against targetData and buffer_data_view_...
    UNSAFE_TODO(memcpy(static_cast<uint8_t*>(targetData.data()) + offset,
                       buffer_data_view_.data() + offset, size));
    return true;
}

The targetData comes from GetTarget(), which returns a span over mTargetData, the now-dangling pointer. The source data (buffer_data_view_) points into renderer-controlled shared memory.

The gap exists because PreHandleBufferDestroy does clean up the WriteHandle on individual buffer destruction, but DoDeviceDestroy does not invoke equivalent cleanup for each buffer owned by the device. There is no iteration over associated buffers, no clearing of writeHandle, and no resetting of mapWriteState.

A compromised renderer exploits this by reordering the wire commands. In normal Blink code, GPUDevice::destroy() calls UnmapAllMappableBuffers() (which sends BufferUpdateMappedData) before GetHandle().Destroy() (which sends DeviceDestroy). By reversing this order, the renderer ensures DeviceDestroy frees the memory before BufferUpdateMappedData writes to it.

Reproduce

Tested on commit 3484f09b1620f6b7198fda97caf21b822d3df8ff on macOS and Ubuntu 22.04. This vulnerability exists on all platforms that support WebGPU (Windows, macOS, Linux, ChromeOS, Android).

Configure an ASAN build. A minimal args.gn for out/asan:

is_asan = true
is_debug = false
is_component_build = false

Build Chrome:

git apply patch.diff
autoninja -C out/asan chrome

Run Chrome:

# macOS
out/asan/Chromium.app/Contents/MacOS/Chromium --user-data-dir=./userdata poc.html

# Linux
out/asan/chrome --enable-unsafe-webgpu --user-data-dir=./userdata poc.html

On macOS, the native Metal backend allocates GPU buffer memory through vm_allocate/IOKit mapped memory, which lives outside the heap regions that ASAN instruments. When the buffer is destroyed and the VM pages are reclaimed by the OS, the subsequent memcpy hits unmapped virtual memory and produces a SEGV rather than an ASAN report. On Linux, --enable-unsafe-webgpu selects SwiftShader as the Vulkan backend. SwiftShader allocates GPU buffer memory via malloc (through sw::allocateZeroOrPoison), which ASAN fully instruments, so the dangling write produces a proper heap-use-after-free report. To obtain an ASAN report on macOS, pass --use-webgpu-adapter=swiftshader --enable-unsafe-webgpu to force the SwiftShader backend.

SEGV on macOS (native Metal backend):

Received signal 11 SEGV_ACCERR 0001341f4000
 [0x00035c2633c8]
 [0x00035c237198]
 [0x00035c2631fc]
 [0x000180743744]
 [0x000104a41994]
 [0x0003623fe9dc]
 [0x0003623c0ee0]
 [0x0003623cd95c]
 [0x0003623d712c]
 [0x00036240ada4]
 [0x00036240b1a8]
 [0x000362401144]
 [0x000351a5af0c]
 [0x000362316cc0]
 [0x000362315e10]
 [0x00036233470c]
 [0x0003623403ac]
 [0x0003623401c4]
 [0x000351a93d5c]
 [0x000351a6e83c]
 [0x000351a6ced4]
 [0x000351a70270]
 [0x00035c0fb574]
 [0x00035c163318]
 [0x00035c1626d0]
 [0x00035c284a8c]
 [0x00035c2761d8]
 [0x00035c282edc]
 [0x0001807d89f8]
 [0x0001807d898c]
 [0x0001807d86f8]
 [0x0001807d7388]
 [0x000180891e34]
 [0x000182a26964]
 [0x00035c285bdc]
 [0x00035c281c44]
 [0x00035c164678]
 [0x00035c089820]
 [0x00036546ae9c]
 [0x0003587c9b64]
 [0x0003587cbce4]
 [0x0003587c7854]
 [0x0003587c7d44]
 [0x000349c4dcb8]
 [0x000104640c98]
 [0x000180371d54]
[end of stack trace]

ASAN output on Linux (SwiftShader backend via --enable-unsafe-webgpu):

=================================================================
==154737==ERROR: AddressSanitizer: heap-use-after-free on address 0x71787d175900 at pc 0x5eec4ca260ae bp 0x7ffe523cc350 sp 0x7ffe523cbb10
WRITE of size 16777216 at 0x71787d175900 thread T0 (chrome)
    #0 0x5eec4ca260ad in __asan_memcpy (/home/test/Desktop/chromium/src/out/asan/chrome+0x10e810ad) (BuildId: 285b4624b4b2a3a5)
    #1 0x5eec6e07c0a4 in gpu::webgpu::(anonymous namespace)::WriteHandleImpl::DeserializeDataUpdate(void const*, unsigned long, unsigned long, unsigned long) gpu/command_buffer/service/dawn_service_memory_transfer_service.cc:123:17
    #2 0x5eec6e0c9709 in dawn::wire::server::Server::DoBufferUpdateMappedData(dawn::wire::server::Known<WGPUBufferImpl*>, unsigned long, unsigned char const*, unsigned long, unsigned long) third_party/dawn/src/dawn/wire/server/ServerBuffer.cpp:248:37
    #3 0x5eec6e0a68eb in dawn::wire::server::Server::HandleBufferUpdateMappedData(dawn::wire::DeserializeBuffer*) gen/third_party/dawn/src/dawn/wire/server/ServerHandlers_autogen.cpp:101:18
    #4 0x5eec6e0b1c8a in dawn::wire::server::Server::HandleCommands(char const volatile*, unsigned long) gen/third_party/dawn/src/dawn/wire/server/ServerHandlers_autogen.cpp:1676:30
    #5 0x5eec6e06c317 in gpu::webgpu::(anonymous namespace)::DawnWireServer::HandleCommands(char const volatile*, unsigned long) gpu/command_buffer/service/webgpu_decoder_impl.cc:155:33
    #6 0x5eec6e06c78d in gpu::webgpu::(anonymous namespace)::WebGPUDecoderImpl::HandleDawnCommands(unsigned int, void const volatile*) gpu/command_buffer/service/webgpu_decoder_impl.cc:1988:22
    #7 0x5eec6e060682 in gpu::webgpu::(anonymous namespace)::WebGPUDecoderImpl::DoCommands(unsigned int, void const volatile*, int, int*) gpu/command_buffer/service/webgpu_decoder_impl.cc:1933:18
    #8 0x5eec5763b814 in gpu::CommandBufferService::Flush(int, gpu::AsyncAPIInterface*) gpu/command_buffer/service/command_buffer_service.cc:267:35
    #9 0x5eec6d79a62b in gpu::CommandBufferStub::OnAsyncFlush(int, unsigned int, std::__Cr::vector<gpu::SyncToken, std::__Cr::allocator<gpu::SyncToken>> const&) gpu/ipc/service/command_buffer_stub.cc:504:22
    #10 0x5eec6d799891 in gpu::CommandBufferStub::ExecuteDeferredRequest(gpu::mojom::DeferredCommandBufferRequestParams&, gpu::FenceSyncReleaseDelegate*) gpu/ipc/service/command_buffer_stub.cc:173:7
    #11 0x5eec6d7bc97c in gpu::GpuChannel::ExecuteDeferredRequest(mojo::StructPtr<gpu::mojom::DeferredRequestParams>, gpu::FenceSyncReleaseDelegate*) gpu/ipc/service/gpu_channel.cc:833:13
    #12 0x5eec6d7ca967 in void base::internal::DecayedFunctorTraits<void (gpu::GpuChannel::*)(mojo::StructPtr<gpu::mojom::DeferredRequestParams>, gpu::FenceSyncReleaseDelegate*), base::WeakPtr<gpu::GpuChannel>&&, mojo::StructPtr<gpu::mojom::DeferredRequestParams>&&>::Invoke<void (gpu::GpuChannel::*)(mojo::StructPtr<gpu::mojom::DeferredRequestParams>, gpu::FenceSyncReleaseDelegate*), base::WeakPtr<gpu::GpuChannel> const&, mojo::StructPtr<gpu::mojom::DeferredRequestParams>, gpu::FenceSyncReleaseDelegate*>(void (gpu::GpuChannel::*)(mojo::StructPtr<gpu::mojom::DeferredRequestParams>, gpu::FenceSyncReleaseDelegate*), base::WeakPtr<gpu::GpuChannel> const&, mojo::StructPtr<gpu::mojom::DeferredRequestParams>&&, gpu::FenceSyncReleaseDelegate*&&) base/functional/bind_internal.h:740:12
    #13 0x5eec6d7ca749 in base::internal::Invoker<base::internal::FunctorTraits<void (gpu::GpuChannel::*&&)(mojo::StructPtr<gpu::mojom::DeferredRequestParams>, gpu::FenceSyncReleaseDelegate*), base::WeakPtr<gpu::GpuChannel>&&, mojo::StructPtr<gpu::mojom::DeferredRequestParams>&&>, base::internal::BindState<true, true, false, void (gpu::GpuChannel::*)(mojo::StructPtr<gpu::mojom::DeferredRequestParams>, gpu::FenceSyncReleaseDelegate*), base::WeakPtr<gpu::GpuChannel>, mojo::StructPtr<gpu::mojom::DeferredRequestParams>>, void (gpu::FenceSyncReleaseDelegate*)>::RunOnce(base::internal::BindStateBase*, gpu::FenceSyncReleaseDelegate*) base/functional/bind_internal.h:956:5
    #14 0x5eec5767e211 in void base::internal::Invoker<base::internal::FunctorTraits<base::OnceCallback<void (gpu::FenceSyncReleaseDelegate*)>&&, gpu::FenceSyncReleaseDelegate*>, base::internal::BindState<false, true, true, base::OnceCallback<void (gpu::FenceSyncReleaseDelegate*)>, base::internal::UnretainedWrapper<gpu::FenceSyncReleaseDelegate, base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>>, void ()>::RunImpl<base::OnceCallback<void (gpu::FenceSyncReleaseDelegate*)>, std::__Cr::tuple<base::internal::UnretainedWrapper<gpu::FenceSyncReleaseDelegate, base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>>, 0ul>(base::OnceCallback<void (gpu::FenceSyncReleaseDelegate*)>&&, std::__Cr::tuple<base::internal::UnretainedWrapper<gpu::FenceSyncReleaseDelegate, base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>>&&, std::__Cr::integer_sequence<unsigned long, 0ul>) base/functional/callback.h:155:12
    #15 0x5eec57652957 in gpu::Scheduler::ExecuteSequence(base::IdType<gpu::SyncPointOrderData, unsigned int, 0u, 1u>) base/functional/callback.h:155:12
    #16 0x5eec57650988 in gpu::Scheduler::RunNextTask() gpu/command_buffer/service/scheduler.cc:625:3
    #17 0x5eec57654571 in base::internal::Invoker<base::internal::FunctorTraits<void (gpu::Scheduler::*&&)(), gpu::Scheduler*>, base::internal::BindState<true, true, false, void (gpu::Scheduler::*)(), base::internal::UnretainedWrapper<gpu::Scheduler, base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>>, void ()>::RunOnce(base::internal::BindStateBase*) base/functional/bind_internal.h:740:12
    #18 0x5eec64210c76 in base::TaskAnnotator::RunTaskImpl(base::PendingTask&) base/functional/callback.h:155:12
    #19 0x5eec64288459 in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWorkImpl(base::LazyNow*) base/task/common/task_annotator.h:112:5
    #20 0x5eec642872ca in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWork() base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:346:40
    #21 0x5eec64435e74 in base::MessagePumpGlib::Run(base::MessagePump::Delegate*) base/message_loop/message_pump_glib.cc:782:48
    #22 0x5eec64289b67 in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::Run(bool, base::TimeDelta) base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:650:12
    #23 0x5eec6418c210 in base::RunLoop::Run(base::Location const&) base/run_loop.cc:135:14
    #24 0x5eec6fb5379c in content::GpuMain(content::MainFunctionParams) content/gpu/gpu_main.cc:479:14
    #25 0x5eec5fe67e3f in content::RunZygote(content::ContentMainDelegate*) content/app/content_main_runner_impl.cc:664:14
    #26 0x5eec5fe6916f in content::RunOtherNamedProcessTypeMain(std::__Cr::basic_string<char, std::__Cr::char_traits<char>, std::__Cr::allocator<char>> const&, content::MainFunctionParams, content::ContentMainDelegate*) content/app/content_main_runner_impl.cc:771:12
    #27 0x5eec5fe6be78 in content::ContentMainRunnerImpl::Run() content/app/content_main_runner_impl.cc:1152:10
    #28 0x5eec5fe65851 in content::RunContentProcess(content::ContentMainParams, content::ContentMainRunner*) content/app/content_main.cc:358:36
    #29 0x5eec5fe65e4c in content::ContentMain(content::ContentMainParams) content/app/content_main.cc:371:10
    #30 0x5eec4ca62b39 in ChromeMain chrome/app/chrome_main.cc:191:12
    #31 0x757a1a229d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16

0x71787d175900 is located 256 bytes inside of 16777495-byte region [0x71787d175800,0x71787e175917)
freed by thread T0 (chrome) here:
    #0 0x5eec4ca28086 in free (/home/test/Desktop/chromium/src/out/asan/chrome+0x10e83086) (BuildId: 285b4624b4b2a3a5)
    #1 0x71789987f42c in vk::DeviceMemory::freeBuffer() third_party/swiftshader/src/Vulkan/VkDeviceMemory.cpp:354:2
    #2 0x71789987f1f8 in vk::DeviceMemory::destroy(VkAllocationCallbacks const*) third_party/swiftshader/src/Vulkan/VkDeviceMemory.cpp:162:3
    #3 0x7178998d37af in vkFreeMemory third_party/swiftshader/src/Vulkan/VkDestroy.hpp:61:11
    #4 0x5eec4ec44fcf in dawn::native::vulkan::FencedDeleter::UpdateCompletedSerialTo(unsigned long) third_party/dawn/src/dawn/native/vulkan/FencedDeleter.cpp:88:30
    #5 0x5eec4e8fd68f in dawn::native::ExecutionQueueBase::WaitForIdleForDestruction() third_party/dawn/src/dawn/native/ExecutionQueue.cpp:184:24
    #6 0x5eec4e883c05 in dawn::native::DeviceBase::Destroy(dawn::native::DestroyReason) third_party/dawn/src/dawn/native/Device.cpp:681:34
    #7 0x5eec4e71e8a2 in dawn::native::NativeDeviceDestroy(WGPUDeviceImpl*) gen/third_party/dawn/src/dawn/native/ProcTable.cpp:946:15
    #8 0x5eec6e0be92d in dawn::wire::server::Server::DoDeviceDestroy(WGPUDeviceImpl*) gen/third_party/dawn/src/dawn/wire/server/ServerDoers_autogen.cpp:494:9
    #9 0x5eec6e0b323b in dawn::wire::server::Server::HandleCommands(char const volatile*, unsigned long) gen/third_party/dawn/src/dawn/wire/server/ServerHandlers_autogen.cpp:692:18
    #10 0x5eec6e06c317 in gpu::webgpu::(anonymous namespace)::DawnWireServer::HandleCommands(char const volatile*, unsigned long) gpu/command_buffer/service/webgpu_decoder_impl.cc:155:33
    #11 0x5eec6e06c78d in gpu::webgpu::(anonymous namespace)::WebGPUDecoderImpl::HandleDawnCommands(unsigned int, void const volatile*) gpu/command_buffer/service/webgpu_decoder_impl.cc:1988:22
    #12 0x5eec6e060682 in gpu::webgpu::(anonymous namespace)::WebGPUDecoderImpl::DoCommands(unsigned int, void const volatile*, int, int*) gpu/command_buffer/service/webgpu_decoder_impl.cc:1933:18
    #13 0x5eec5763b814 in gpu::CommandBufferService::Flush(int, gpu::AsyncAPIInterface*) gpu/command_buffer/service/command_buffer_service.cc:267:35
    #14 0x5eec6d79a62b in gpu::CommandBufferStub::OnAsyncFlush(int, unsigned int, std::__Cr::vector<gpu::SyncToken, std::__Cr::allocator<gpu::SyncToken>> const&) gpu/ipc/service/command_buffer_stub.cc:504:22
    #15 0x5eec6d799891 in gpu::CommandBufferStub::ExecuteDeferredRequest(gpu::mojom::DeferredCommandBufferRequestParams&, gpu::FenceSyncReleaseDelegate*) gpu/ipc/service/command_buffer_stub.cc:173:7
    #16 0x5eec6d7bc97c in gpu::GpuChannel::ExecuteDeferredRequest(mojo::StructPtr<gpu::mojom::DeferredRequestParams>, gpu::FenceSyncReleaseDelegate*) gpu/ipc/service/gpu_channel.cc:833:13
    #17 0x5eec6d7ca967 in void base::internal::DecayedFunctorTraits<void (gpu::GpuChannel::*)(mojo::StructPtr<gpu::mojom::DeferredRequestParams>, gpu::FenceSyncReleaseDelegate*), base::WeakPtr<gpu::GpuChannel>&&, mojo::StructPtr<gpu::mojom::DeferredRequestParams>&&>::Invoke<void (gpu::GpuChannel::*)(mojo::StructPtr<gpu::mojom::DeferredRequestParams>, gpu::FenceSyncReleaseDelegate*), base::WeakPtr<gpu::GpuChannel> const&, mojo::StructPtr<gpu::mojom::DeferredRequestParams>, gpu::FenceSyncReleaseDelegate*>(void (gpu::GpuChannel::*)(mojo::StructPtr<gpu::mojom::DeferredRequestParams>, gpu::FenceSyncReleaseDelegate*), base::WeakPtr<gpu::GpuChannel> const&, mojo::StructPtr<gpu::mojom::DeferredRequestParams>&&, gpu::FenceSyncReleaseDelegate*&&) base/functional/bind_internal.h:740:12
    #18 0x5eec6d7ca749 in base::internal::Invoker<base::internal::FunctorTraits<void (gpu::GpuChannel::*&&)(mojo::StructPtr<gpu::mojom::DeferredRequestParams>, gpu::FenceSyncReleaseDelegate*), base::WeakPtr<gpu::GpuChannel>&&, mojo::StructPtr<gpu::mojom::DeferredRequestParams>&&>, base::internal::BindState<true, true, false, void (gpu::GpuChannel::*)(mojo::StructPtr<gpu::mojom::DeferredRequestParams>, gpu::FenceSyncReleaseDelegate*), base::WeakPtr<gpu::GpuChannel>, mojo::StructPtr<gpu::mojom::DeferredRequestParams>>, void (gpu::FenceSyncReleaseDelegate*)>::RunOnce(base::internal::BindStateBase*, gpu::FenceSyncReleaseDelegate*) base/functional/bind_internal.h:956:5
    #19 0x5eec5767e211 in void base::internal::Invoker<base::internal::FunctorTraits<base::OnceCallback<void (gpu::FenceSyncReleaseDelegate*)>&&, gpu::FenceSyncReleaseDelegate*>, base::internal::BindState<false, true, true, base::OnceCallback<void (gpu::FenceSyncReleaseDelegate*)>, base::internal::UnretainedWrapper<gpu::FenceSyncReleaseDelegate, base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>>, void ()>::RunImpl<base::OnceCallback<void (gpu::FenceSyncReleaseDelegate*)>, std::__Cr::tuple<base::internal::UnretainedWrapper<gpu::FenceSyncReleaseDelegate, base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>>, 0ul>(base::OnceCallback<void (gpu::FenceSyncReleaseDelegate*)>&&, std::__Cr::tuple<base::internal::UnretainedWrapper<gpu::FenceSyncReleaseDelegate, base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>>&&, std::__Cr::integer_sequence<unsigned long, 0ul>) base/functional/callback.h:155:12
    #20 0x5eec57652957 in gpu::Scheduler::ExecuteSequence(base::IdType<gpu::SyncPointOrderData, unsigned int, 0u, 1u>) base/functional/callback.h:155:12
    #21 0x5eec57650988 in gpu::Scheduler::RunNextTask() gpu/command_buffer/service/scheduler.cc:625:3
    #22 0x5eec57654571 in base::internal::Invoker<base::internal::FunctorTraits<void (gpu::Scheduler::*&&)(), gpu::Scheduler*>, base::internal::BindState<true, true, false, void (gpu::Scheduler::*)(), base::internal::UnretainedWrapper<gpu::Scheduler, base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>>, void ()>::RunOnce(base::internal::BindStateBase*) base/functional/bind_internal.h:740:12
    #23 0x5eec64210c76 in base::TaskAnnotator::RunTaskImpl(base::PendingTask&) base/functional/callback.h:155:12
    #24 0x5eec64288459 in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWorkImpl(base::LazyNow*) base/task/common/task_annotator.h:112:5
    #25 0x5eec642872ca in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWork() base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:346:40
    #26 0x5eec64435538 in base::MessagePumpGlib::HandleDispatch() base/message_loop/message_pump_glib.cc:736:46
    #27 0x5eec64438af8 in base::(anonymous namespace)::WorkSourceDispatch(_GSource*, int (*)(void*), void*) base/message_loop/message_pump_glib.cc:355:43
    #28 0x757a1b72dd3a in g_main_context_dispatch (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x55d3a) (BuildId: 6b4f160dbc5397c2f502dc4f08a8cff259917926)

previously allocated by thread T0 (chrome) here:
    #0 0x5eec4ca28324 in malloc (/home/test/Desktop/chromium/src/out/asan/chrome+0x10e83324) (BuildId: 285b4624b4b2a3a5)
    #1 0x717899d6bf1d in sw::allocateZeroOrPoison(unsigned long, unsigned long) third_party/swiftshader/src/System/Memory.cpp:81:42
    #2 0x71789987f3c4 in vk::DeviceMemory::allocateBuffer() third_party/swiftshader/src/Vulkan/VkDeviceMemory.cpp:342:11
    #3 0x71789987e024 in vk::DeviceMemory::Allocate(VkAllocationCallbacks const*, VkMemoryAllocateInfo const*, VkNonDispatchableHandle<VkDeviceMemory_T*>*, vk::Device*) third_party/swiftshader/src/Vulkan/VkDeviceMemory.cpp:275:12
    #4 0x7178998d36a7 in vkAllocateMemory third_party/swiftshader/src/Vulkan/libVulkan.cpp:1421:20
    #5 0x5eec4eca20a9 in dawn::native::vulkan::ResourceMemoryAllocator::SingleTypeAllocator::AllocateResourceHeap(unsigned long) third_party/dawn/src/dawn/native/vulkan/ResourceMemoryAllocatorVk.cpp:113:35
    #6 0x5eec4ec9ebfa in dawn::native::vulkan::ResourceMemoryAllocator::Allocate(VkMemoryRequirements const&, dawn::native::vulkan::MemoryKind, bool) third_party/dawn/src/dawn/native/vulkan/ResourceMemoryAllocatorVk.cpp:241:67
    #7 0x5eec4ec03eb2 in dawn::native::vulkan::Buffer::Initialize(bool) third_party/dawn/src/dawn/native/vulkan/BufferVk.cpp:293:59
    #8 0x5eec4ec02ca6 in dawn::native::vulkan::Buffer::Create(dawn::native::vulkan::Device*, dawn::native::UnpackedPtr<dawn::native::BufferDescriptor> const&) third_party/dawn/src/dawn/native/vulkan/BufferVk.cpp:219:26
    #9 0x5eec4ec316b7 in dawn::native::vulkan::Device::CreateBufferImpl(dawn::native::UnpackedPtr<dawn::native::BufferDescriptor> const&) third_party/dawn/src/dawn/native/vulkan/DeviceVk.cpp:213:12
    #10 0x5eec4e88e50b in dawn::native::DeviceBase::APICreateBuffer(dawn::native::BufferDescriptor const*) third_party/dawn/src/dawn/native/Device.cpp:1339:20
    #11 0x5eec6e0c8c2c in dawn::wire::server::Server::DoDeviceCreateBuffer(dawn::wire::server::Known<WGPUDeviceImpl*>, WGPUBufferDescriptor const*, dawn::wire::ObjectHandle, unsigned long, unsigned char const*, unsigned long, unsigned char const*) third_party/dawn/src/dawn/wire/server/ServerBuffer.cpp:142:22
    #12 0x5eec6e0a831c in dawn::wire::server::Server::HandleDeviceCreateBuffer(dawn::wire::DeserializeBuffer*) gen/third_party/dawn/src/dawn/wire/server/ServerHandlers_autogen.cpp:463:18
    #13 0x5eec6e0b0648 in dawn::wire::server::Server::HandleCommands(char const volatile*, unsigned long) gen/third_party/dawn/src/dawn/wire/server/ServerHandlers_autogen.cpp:1778:30
    #14 0x5eec6e06c317 in gpu::webgpu::(anonymous namespace)::DawnWireServer::HandleCommands(char const volatile*, unsigned long) gpu/command_buffer/service/webgpu_decoder_impl.cc:155:33
    #15 0x5eec6e06c78d in gpu::webgpu::(anonymous namespace)::WebGPUDecoderImpl::HandleDawnCommands(unsigned int, void const volatile*) gpu/command_buffer/service/webgpu_decoder_impl.cc:1988:22
    #16 0x5eec6e060682 in gpu::webgpu::(anonymous namespace)::WebGPUDecoderImpl::DoCommands(unsigned int, void const volatile*, int, int*) gpu/command_buffer/service/webgpu_decoder_impl.cc:1933:18
    #17 0x5eec5763b814 in gpu::CommandBufferService::Flush(int, gpu::AsyncAPIInterface*) gpu/command_buffer/service/command_buffer_service.cc:267:35
    #18 0x5eec6d79a62b in gpu::CommandBufferStub::OnAsyncFlush(int, unsigned int, std::__Cr::vector<gpu::SyncToken, std::__Cr::allocator<gpu::SyncToken>> const&) gpu/ipc/service/command_buffer_stub.cc:504:22
    #19 0x5eec6d799891 in gpu::CommandBufferStub::ExecuteDeferredRequest(gpu::mojom::DeferredCommandBufferRequestParams&, gpu::FenceSyncReleaseDelegate*) gpu/ipc/service/command_buffer_stub.cc:173:7
    #20 0x5eec6d7bc97c in gpu::GpuChannel::ExecuteDeferredRequest(mojo::StructPtr<gpu::mojom::DeferredRequestParams>, gpu::FenceSyncReleaseDelegate*) gpu/ipc/service/gpu_channel.cc:833:13
    #21 0x5eec6d7ca967 in void base::internal::DecayedFunctorTraits<void (gpu::GpuChannel::*)(mojo::StructPtr<gpu::mojom::DeferredRequestParams>, gpu::FenceSyncReleaseDelegate*), base::WeakPtr<gpu::GpuChannel>&&, mojo::StructPtr<gpu::mojom::DeferredRequestParams>&&>::Invoke<void (gpu::GpuChannel::*)(mojo::StructPtr<gpu::mojom::DeferredRequestParams>, gpu::FenceSyncReleaseDelegate*), base::WeakPtr<gpu::GpuChannel> const&, mojo::StructPtr<gpu::mojom::DeferredRequestParams>, gpu::FenceSyncReleaseDelegate*>(void (gpu::GpuChannel::*)(mojo::StructPtr<gpu::mojom::DeferredRequestParams>, gpu::FenceSyncReleaseDelegate*), base::WeakPtr<gpu::GpuChannel> const&, mojo::StructPtr<gpu::mojom::DeferredRequestParams>&&, gpu::FenceSyncReleaseDelegate*&&) base/functional/bind_internal.h:740:12
    #22 0x5eec6d7ca749 in base::internal::Invoker<base::internal::FunctorTraits<void (gpu::GpuChannel::*&&)(mojo::StructPtr<gpu::mojom::DeferredRequestParams>, gpu::FenceSyncReleaseDelegate*), base::WeakPtr<gpu::GpuChannel>&&, mojo::StructPtr<gpu::mojom::DeferredRequestParams>&&>, base::internal::BindState<true, true, false, void (gpu::GpuChannel::*)(mojo::StructPtr<gpu::mojom::DeferredRequestParams>, gpu::FenceSyncReleaseDelegate*), base::WeakPtr<gpu::GpuChannel>, mojo::StructPtr<gpu::mojom::DeferredRequestParams>>, void (gpu::FenceSyncReleaseDelegate*)>::RunOnce(base::internal::BindStateBase*, gpu::FenceSyncReleaseDelegate*) base/functional/bind_internal.h:956:5
    #23 0x5eec5767e211 in void base::internal::Invoker<base::internal::FunctorTraits<base::OnceCallback<void (gpu::FenceSyncReleaseDelegate*)>&&, gpu::FenceSyncReleaseDelegate*>, base::internal::BindState<false, true, true, base::OnceCallback<void (gpu::FenceSyncReleaseDelegate*)>, base::internal::UnretainedWrapper<gpu::FenceSyncReleaseDelegate, base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>>, void ()>::RunImpl<base::OnceCallback<void (gpu::FenceSyncReleaseDelegate*)>, std::__Cr::tuple<base::internal::UnretainedWrapper<gpu::FenceSyncReleaseDelegate, base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>>, 0ul>(base::OnceCallback<void (gpu::FenceSyncReleaseDelegate*)>&&, std::__Cr::tuple<base::internal::UnretainedWrapper<gpu::FenceSyncReleaseDelegate, base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>>&&, std::__Cr::integer_sequence<unsigned long, 0ul>) base/functional/callback.h:155:12
    #24 0x5eec57652957 in gpu::Scheduler::ExecuteSequence(base::IdType<gpu::SyncPointOrderData, unsigned int, 0u, 1u>) base/functional/callback.h:155:12
    #25 0x5eec57650988 in gpu::Scheduler::RunNextTask() gpu/command_buffer/service/scheduler.cc:625:3
    #26 0x5eec57654571 in base::internal::Invoker<base::internal::FunctorTraits<void (gpu::Scheduler::*&&)(), gpu::Scheduler*>, base::internal::BindState<true, true, false, void (gpu::Scheduler::*)(), base::internal::UnretainedWrapper<gpu::Scheduler, base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>>, void ()>::RunOnce(base::internal::BindStateBase*) base/functional/bind_internal.h:740:12
    #27 0x5eec64210c76 in base::TaskAnnotator::RunTaskImpl(base::PendingTask&) base/functional/callback.h:155:12
    #28 0x5eec64288459 in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWorkImpl(base::LazyNow*) base/task/common/task_annotator.h:112:5
    #29 0x5eec642872ca in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWork() base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:346:40

SUMMARY: AddressSanitizer: heap-use-after-free (/home/test/Desktop/chromium/src/out/asan/chrome+0x10e810ad) (BuildId: 285b4624b4b2a3a5) in __asan_memcpy
Shadow bytes around the buggy address:
  0x71787d175680: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x71787d175700: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x71787d175780: fa fa fa fa fa fa fa fa fa fa fa fa fa fa f7 fa
  0x71787d175800: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x71787d175880: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
=>0x71787d175900:[fd]fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x71787d175980: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x71787d175a00: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x71787d175a80: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x71787d175b00: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x71787d175b80: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb

==154737==ADDITIONAL INFO

==154737==Note: Please include this section with the ASan report.
Task trace:
    #0 0x5eec57650e62 in gpu::Scheduler::RunNextTask() gpu/command_buffer/service/scheduler.cc:647:27
    #1 0x5eec57650e62 in gpu::Scheduler::RunNextTask() gpu/command_buffer/service/scheduler.cc:647:27
    #2 0x5eec57650e62 in gpu::Scheduler::RunNextTask() gpu/command_buffer/service/scheduler.cc:647:27
    #3 0x5eec57650e62 in gpu::Scheduler::RunNextTask() gpu/command_buffer/service/scheduler.cc:647:27

MiraclePtr Status: NOT PROTECTED
No raw_ptr<T> access to this region was detected prior to this crash.
This crash is still exploitable with MiraclePtr.
Refer to https://chromium.googlesource.com/chromium/src/+/main/base/memory/raw_ptr.md for details.

==154737==END OF ADDITIONAL INFO

==154737==ABORTING
[154700:154700:0312/225514.129998:ERROR:content/browser/gpu/gpu_process_host.cc:999] GPU process exited unexpectedly: exit_code=256

Credit

Please use 86ac1f1587b71893ed2ad792cd7dde32 as the credit for this vulnerability. Thank you.

View on issue tracker