CVE-2025-10500
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifsrc/dawn/native/Device.cpp |
modified |
Files Changed
src/dawn/native/Device.cpp
Patch
From 1237c6eccd8822df3fcd76a10be27234d4ba9918 Mon Sep 17 00:00:00 2001 From: Lokbondo Kung <[email protected]> Date: Wed, 20 Aug 2025 05:01:25 -0700 Subject: [PATCH] [dawn][native] Standardize calling order for graceful device teardown. - Updates all the code-paths to call the graceful device teardown helpers in the same order. - See https://g-issues.chromium.org/issues/435875050#comment13 for breakdown of the logic for ordering it in this manner. Bug: 435875050 Change-Id: Ife7dfcb2c333f9b9bbc2895b683974e96119e73f Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/258175 Auto-Submit: Loko Kung <[email protected]> Commit-Queue: Corentin Wallez <[email protected]> Reviewed-by: Corentin Wallez <[email protected]> --- diff --git a/src/dawn/native/Device.cpp b/src/dawn/native/Device.cpp index dd0fe08..dc78fee 100644 --- a/src/dawn/native/Device.cpp +++ b/src/dawn/native/Device.cpp @@ -638,6 +638,10 @@ // complete before proceeding with destruction. // Ignore errors so that we can continue with destruction IgnoreErrors(mQueue->WaitForIdleForDestruction()); + + // Call TickImpl once last time to clean up resources + // Ignore errors so that we can continue with destruction + IgnoreErrors(TickImpl()); break; case State::BeingDisconnected: @@ -661,10 +665,6 @@ mQueue->AssumeCommandsComplete(); DAWN_ASSERT(mQueue->GetCompletedCommandSerial() == mQueue->GetLastSubmittedCommandSerial()); mQueue->Tick(mQueue->GetCompletedCommandSerial()); - - // Call TickImpl once last time to clean up resources - // Ignore errors so that we can continue with destruction - IgnoreErrors(TickImpl()); } // At this point GPU operations are always finished, so we are in the disconnected state. @@ -682,7 +682,6 @@ // Note: mQueue is not released here since the application may still get it after calling // Destroy() via APIGetQueue. if (mQueue != nullptr) { - mQueue->AssumeCommandsComplete(); mQueue->Destroy(); } @@ -728,6 +727,7 @@ // Disconnected so we can detect this case in WaitForIdleForDestruction. if (ErrorInjectorEnabled()) { IgnoreErrors(mQueue->WaitForIdleForDestruction()); + IgnoreErrors(TickImpl()); } // A real device lost happened. Set the state to disconnected as the device cannot be
Original Bug Report
WebGPU dawn::native::d3d12::ResourceAllocatorManager::Tick Heap-Use-After-Free
Steps to reproduce the problem
Steps to reproduce
- Run open_PoC.bat (launches a local HTTP server and opens chromium)
- Navigates to PoC.html, The flag —no-sandbox is used for ASAN output. ⇒
./chrome.exe --no-sandbox http://localhost:8000/PoC.html - After a few reload cycles, the GPU process crashes. On ASan builds, you will see a heap-use-after-free
open_PoC.bat
@echo off
cd /d %~dp0
start "" python -m http.server 8000
timeout /t 2 >nul
start "" http://localhost:8000/PoC.html
Problem Description
Description
This issue is a heap-use-after-free bug in the Dawn D3D12 backend of Chromium/WebGPU, observed only on Windows systems without a discrete GPU (e.g., integrated GPU or GPU-less notebook environments), triggered by resource lifetime mismanagement during asynchronous GPU command execution.
Impact
When specific WebGPU operations are performed (creating large depth/stencil textures, creating incompatible texture views, and issuing copyTextureToBuffer with intentionally misaligned parameters), the GPU process consistently crashes.
Root Cause
The root cause lies in third_party/dawn/src/dawn/native/d3d12/ResourceAllocatorManagerD3D12.cpp:
void ResourceAllocatorManager::Tick(ExecutionSerial completedSerial) {
for (ResourceHeapAllocation& allocation :
mAllocationsToDelete.IterateUpTo(completedSerial)) {
if (allocation.GetInfo().mMethod == AllocationMethod::kSubAllocated) {
FreeSubAllocatedMemory(allocation);
}
}
mAllocationsToDelete.ClearUpTo(completedSerial);
mHeapsToDelete.ClearUpTo(completedSerial);
}
Tick() is called with a completedSerial that Dawn believes corresponds to all finished GPU work. However, in certain timing conditions, commands that still reference these Heap objects may not yet be fully retired on the GPU. This allows the manager to clear and free heap resources prematurely.
Later, when ResidencyManager::EnsureHeapsAreResident() iterates over those heap pointers to lock residency before executing a new command list, it dereferences stale pointers to already-freed memory. This results in a reproducible use-after-free read (and potentially write) condition.
mitigation suggestion
Use-after-free can be mitigated by introducing a reference counting mechanism for D3D12 Heaps:
retain the Heap while any command list or resource still references it, and only free when the ref-count drops to zero after GPU work completion.
How to Fix
The root cause is that ResourceAllocatorManager::Tick() may free ResourceHeapAllocation objects that are still referenced by in-flight GPU commands.
To fix this, introdue reference counting (or strong ownership tracking) for heap allocations instead of relying solely on completedSerial.
Key steps:
- Add a ref-count to ResourceHeapAllocation / Heap objects
- Increment the ref-count when a command list or texture/buffer references a heap.
- Decrement the ref-count only after the GPU has signaled completion of the work using that heap.
- Modify Tick() to defer freeing
-
In
Tick(), instead of callingFreeSubAllocatedMemory()andClearUpTo()immediately,check whether the ref-count for each
allocationhas reached 0. -
Only free and clear entries when no active references remain.
-
- Validation
- Optionally add debug assertions (in ASan/Debug builds) to ensure that no heap is accessed after free.
- This prevents use-after-free bugs when commands complete later than
completedSerialsuggests.
Pseudo-code adjustment:
void ResourceAllocatorManager::Tick(ExecutionSerial completedSerial) {
for (ResourceHeapAllocation& allocation :
mAllocationsToDelete.IterateUpTo(completedSerial)) {
if (allocation.refCount == 0 &&
allocation.GetInfo().mMethod == AllocationMethod::kSubAllocated) {
FreeSubAllocatedMemory(allocation);
} else {
// Still in use: defer deletion
DeferAllocation(allocation);
}
}
// Clear only those heaps that are confirmed unused
mAllocationsToDelete.RemoveIf([](auto& alloc) { return alloc.refCount == 0; });
mHeapsToDelete.RemoveIf([](auto& heap) { return heap.refCount == 0; });
}
This ensures that heap objects will not be freed prematurely, eliminating the race that leads to the use-after-free.
Summary
WebGPU dawn::native::d3d12::ResourceAllocatorManager::Tick Heap-Use-After-Free
Custom Questions
Type of crash:
see ASan
Crash state:
ASan stack trace
Heap-use-after-free (read) detected by ASan.
READ of size 4 at 0x12a6a8aec854 thread T0
#0 0x7ff8e93af829 in dawn::native::d3d12::Pageable::IsResidencyLocked(void) const C:\b\s\w\ir\cache\builder\src\third_party\dawn\src\dawn\native\d3d12\PageableD3D12.cpp:86:12
#1 0x7ff8e93c8159 in dawn::native::d3d12::ResidencyManager::EnsureHeapsAreResident(class dawn::native::d3d12::Heap **, unsigned __int64) C:\b\s\w\ir\cache\builder\src\third_party\dawn\src\dawn\native\d3d12\ResidencyManagerD3D12.cpp:263:19
#2 0x7ff8e9398acf in dawn::native::d3d12::CommandRecordingContext::ExecuteCommandList(class dawn::native::d3d12::Device *, struct ID3D12CommandQueue *) C:\b\s\w\ir\cache\builder\src\third_party\dawn\src\dawn\native\d3d12\CommandRecordingContext.cpp:77:5
#3 0x7ff8e93bee55 in dawn::native::d3d12::Queue::SubmitPendingCommandsImpl(void) C:\b\s\w\ir\cache\builder\src\third_party\dawn\src\dawn\native\d3d12\QueueD3D12.cpp:133:5
#4 0x7ff8e9163d98 in dawn::native::ExecutionQueueBase::SubmitPendingCommands(void) C:\b\s\w\ir\cache\builder\src\third_party\dawn\src\dawn\native\ExecutionQueue.cpp:112:19
#5 0x7ff8e93a4938 in dawn::native::d3d12::Device::TickImpl(void) C:\b\s\w\ir\cache\builder\src\third_party\dawn\src\dawn\native\d3d12\DeviceD3D12.cpp:366:5
#6 0x7ff8e90c37d9 in dawn::native::DeviceBase::Destroy(void) C:\b\s\w\ir\cache\builder\src\third_party\dawn\src\dawn\native\Device.cpp:665:22
#7 0x7ff8e8fa2258 in dawn::native::NativeDeviceDestroy(struct WGPUDeviceImpl *) C:\b\s\w\ir\cache\builder\src\out\069a-Win_ASan_Releas\gen\third_party\dawn\src\dawn\native\ProcTable.cpp:921:15
...
freed by thread T0 here:
#0 0x7ff96150c584 (C:\Users\wlsrb\Desktop\bugs\chromium-140.0.7317.0-win64-asan\clang_rt.asan_dynamic-x86_64.dll+0x18005c584)
#1 0x7ff8e93af1ce in dawn::native::d3d12::Heap::`scalar deleting dtor'(unsigned int) C:\b\s\w\ir\cache\builder\src\third_party\dawn\src\dawn\native\d3d12\HeapD3D12.h:42:7
#2 0x7ff8e6f83124 in std::__Cr::default_delete<perfetto::internal::TracingMuxerImpl::ConsumerImpl>::operator() C:\b\s\w\ir\cache\builder\src\third_party\libc++\src\include\__memory\unique_ptr.h:77
#3 0x7ff8e6f83124 in std::__Cr::unique_ptr<perfetto::internal::TracingMuxerImpl::ConsumerImpl,std::__Cr::default_delete<perfetto::internal::TracingMuxerImpl::ConsumerImpl> >::reset C:\b\s\w\ir\cache\builder\src\third_party\libc++\src\include\__memory\unique_ptr.h:290
Reporter credit:
Giunash (Gyujeong Jin) of BoB 14th
Additional Data
Category: Security
Chrome Channel: Dev
Regression: N/A \