CVE-2026-11154
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
forsrc/dawn/native/vulkan/QueueVk.cpp |
modified |
Files Changed
src/dawn/native/vulkan/QueueVk.cppsrc/dawn/native/vulkan/QueueVk.h
Patch
From 9f959a0d28d9da35507213936a1a3863d6f0f5d7 Mon Sep 17 00:00:00 2001 From: Brandon Jones <[email protected]> Date: Tue, 14 Apr 2026 14:09:13 -0700 Subject: [PATCH] Vulkan: Avoid potential double-free of VkCommandPool Ensures that the RecordingContext for a Queue is recreated immediately after the command pool/buffers it contained are scheduled for a reset in a SerialTask. This ensures they won't be accidentally double-freed if the OnAfterSubmit fails for any of the specialSyncTextures the recording context contains. Bug: 501789156 Fixed: 501789156 Change-Id: I22a8d4ed09c0382807ae904ea4af19036a0e8773 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/302415 Reviewed-by: Loko Kung <[email protected]> Commit-Queue: Brandon Jones <[email protected]> Auto-Submit: Brandon Jones <[email protected]> Reviewed-by: Kai Ninomiya <[email protected]> --- diff --git a/src/dawn/native/vulkan/QueueVk.cpp b/src/dawn/native/vulkan/QueueVk.cpp index 512a28f..13bbdd3 100644 --- a/src/dawn/native/vulkan/QueueVk.cpp +++ b/src/dawn/native/vulkan/QueueVk.cpp @@ -31,6 +31,7 @@ #include <optional> #include <utility> +#include "absl/cleanup/cleanup.h" #include "dawn/common/Math.h" #include "dawn/native/Buffer.h" #include "dawn/native/CommandValidation.h" @@ -292,6 +293,14 @@ Device* device = ToBackend(GetDevice()); + // Ensure that after calling this method we have a fresh recording context even if one of the + // DAWN_TRY calls below fail. + absl::Cleanup recycleContext = [&]() { + [[maybe_unused]] bool hadError = GetDevice()->ConsumedError( + RecycleRecordingContext(), "Recycling recording context after submit failed for %s", + this); + }; + if (!mRecordingContext.mappableBuffersForEagerTransition.empty()) { // Transition mappable buffers back to map usages with the submit. Buffer::TransitionMappableBuffersEagerly( @@ -338,8 +347,17 @@ device->GetFencedDeleter()->DeleteWhenUnused(semaphore); } IncrementLastSubmittedCommandSerial(); + mFencesInFlight->emplace_back(fence, GetLastSubmittedCommandSerial()); + + for (auto texture : mRecordingContext.specialSyncTextures) { + DAWN_TRY(texture->OnAfterSubmit()); + } + + return {}; +} + +MaybeError Queue::RecycleRecordingContext() { ExecutionSerial lastSubmittedSerial = GetLastSubmittedCommandSerial(); - mFencesInFlight->emplace_back(fence, lastSubmittedSerial); for (size_t i = 0; i < mRecordingContext.commandBufferList.size(); ++i) { CommandPoolAndBuffer commands = {mRecordingContext.commandPoolList[i], @@ -360,10 +378,6 @@ }); } - for (auto texture : mRecordingContext.specialSyncTextures) { - DAWN_TRY(texture->OnAfterSubmit()); - } - mRecordingContext = CommandRecordingContext(); DAWN_TRY(PrepareRecordingContext()); diff --git a/src/dawn/native/vulkan/QueueVk.h b/src/dawn/native/vulkan/QueueVk.h index 6f8159c..96a5b64 100644 --- a/src/dawn/native/vulkan/QueueVk.h +++ b/src/dawn/native/vulkan/QueueVk.h @@ -69,6 +69,7 @@ void ForceEventualFlushOfCommands() override; MaybeError WaitForIdleForDestructionImpl() override; MaybeError SubmitPendingCommandsImpl() override; + MaybeError RecycleRecordingContext(); void DestroyImpl(DestroyReason reason) override; // Dawn API
Original Bug Report
Potential Double-Free of VkCommandPool in Dawn Vulkan backend via error path
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports without the Chrome Security team.
Overview: A logic error in Dawn’s Vulkan backend allows a VkCommandPool to be registered for destruction twice if an error occurs during OnAfterSubmit(). This leads to a potential double-free of driver-allocated memory, which could allow for remote code execution in the GPU process.
Affected files:
third_party/dawn/src/dawn/native/vulkan/QueueVk.cppthird_party/dawn/src/dawn/native/ExecutionQueue.cppthird_party/dawn/src/dawn/native/Device.cppthird_party/dawn/src/dawn/native/vulkan/TextureVk.cpp
Estimated timestamp from git blame: 2026-03-02
Summary
A logic error in the error-handling path of dawn::native::vulkan::Queue::SubmitPendingCommandsImpl can lead to a double-free of a VkCommandPool and VkCommandBuffer handle. This occurs because an early return bypasses the reset of the recording context, causing the same Vulkan handles to be destroyed once by a scheduled cleanup task and a second time during device destruction. This vulnerability could potentially be triggered from a compromised renderer process and may lead to remote code execution in the GPU process.
Technical Details
In third_party/dawn/src/dawn/native/vulkan/QueueVk.cpp, the SubmitPendingCommandsImpl function handles the submission of recorded command buffers to the Vulkan queue. The issue unfolds as follows:
- Task Registration: After a successful
vkQueueSubmit, the function registers aTrackSerialTask(line 347). This task captures the currentVkCommandPoolandVkCommandBufferby value. When the task executes, it intends to reset the command pool and push it intomUnusedCommandsfor recycling. If the reset fails, it destroys the pool directly. - Error Path & State Leak: The function then calls
texture->OnAfterSubmit()for special sync textures (line 364). If this fails (e.g., due to file descriptor exhaustion when exporting a semaphore), theDAWN_TRYmacro forces an early return. Crucially, this early return skips the code that resetsmRecordingContext(line 367). As a result,mRecordingContext.usedremainstrue, and it retains the exact Vulkan handles that were just scheduled for recycling in the task. - Device Destruction Sequence: The propagated error causes the device to transition to a lost state and initiate an emergency shutdown via
mQueue->WaitForIdleForDestruction(). - First Registration for Destruction: Inside
WaitForIdleForDestructionImpl()(QueueVk.cpp:168), the code checks ifmRecordingContext.usedis true. Because of the state leak in step 2, it is true. The function then constructs aCommandPoolAndBufferfrom the stale context and pushes it intomUnusedCommandsfor cleanup during shutdown. - First Free: Next, the execution queue drains pending tasks, including the
TrackSerialTaskfrom step 1. Inside the task,vkResetCommandPoolis called but fails because the device is now in a lost state (VK_ERROR_DEVICE_LOST). As per its error handling (QueueVk.cpp:355), the task callsDestroyCommandPoolAndBuffer, which legitimately frees the Vulkan handles. - Second Free (Double-Free/UAF): Finally, during
Queue::DestroyImpl(QueueVk.cpp:426), Dawn iterates overmUnusedCommands. It encounters the entry that was pushed in step 4. It callsDestroyCommandPoolAndBufferon it, passing the exact same Vulkan handles that were already freed in step 5.
Impact
Double-destruction of a Vulkan handle is undefined behavior. In most Vulkan driver implementations, VkCommandPool represents a heap allocation, and this sequence results in a double-free in the driver’s memory allocator. Additionally, the second call to vkFreeCommandBuffers will attempt to dereference the already-freed pool handle, leading to a Use-After-Free (UAF) condition.
Because VkCommandPool handles represent driver-internal memory, this corruption occurs completely outside of the PartitionAlloc heap and is not protected by MiraclePtr. On platforms where the GPU process has elevated privileges or a weaker sandbox (such as Android), this provides a potential path for a compromised renderer to achieve a sandbox escape.
Suggested Reproduction Steps
Note: These are theoretical steps; our tooling agent does not currently run code to verify them.
- From a compromised renderer, create a WebGPU device using the Vulkan backend.
- Create or import a shared texture (e.g., via
importExternalTextureor a SharedImage-backed texture) so it is managed as anImportedTextureBase. - Deliberately exhaust the file descriptor (FD) table in the GPU process (e.g., by repeatedly creating AHardwareBuffer-backed SharedImages via the GPU channel without releasing them).
- Submit a command buffer using the shared texture. The initial
vkQueueSubmitwill succeed, but the subsequentOnAfterSubmitwill fail whenvkGetSemaphoreFdKHRcannot allocate a new FD, triggering the early return. - The device will transition to a lost state and initiate destruction, executing the double-free during cleanup.
Suggested Fix
Ensure that mRecordingContext is always properly reset, even on error paths. This could be achieved by using an RAII helper (scope guard) within SubmitPendingCommandsImpl to guarantee that mRecordingContext = CommandRecordingContext(); executes regardless of how the function exits, preventing stale handles from being picked up during destruction.
Evaluated with Chrome root at commit: 096fc8fdbfacf2546485756d03f160a3d04fcc9b
Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.