CVE-2026-9874
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifsrc/dawn/native/vulkan/BindGroupLayoutVk.cpp |
modified | |
forsrc/dawn/native/vulkan/BindGroupLayoutVk.cpp |
modified |
Files Changed
src/dawn/native/vulkan/BindGroupLayoutVk.cppsrc/dawn/native/vulkan/BindGroupLayoutVk.h
Patch
From cea524cfcd4c24764cc2a2dce19290ecb1031b27 Mon Sep 17 00:00:00 2001
From: Lokbondo Kung <lokokung@google.com>
Date: Fri, 10 Apr 2026 06:52:26 -0700
Subject: [PATCH] [native][vulkan] Wrap BindGroupLayout cache with a mutex.
- Because we dynamically create BGLs when initializing pipelines,
and because pipelines may be created asynchronously, we need to
make sure that access to the cache map doesn't mangle the data.
Bug: 500609038
Change-Id: I6ffc093773c1c8308bbc95f62da1f202622908db
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/301935
Auto-Submit: Loko Kung <lokokung@google.com>
Reviewed-by: Kyle Charbonneau <kylechar@google.com>
Commit-Queue: Kyle Charbonneau <kylechar@google.com>
---
diff --git a/src/dawn/native/vulkan/BindGroupLayoutVk.cpp b/src/dawn/native/vulkan/BindGroupLayoutVk.cpp
index cccb3f0..226e369 100644
--- a/src/dawn/native/vulkan/BindGroupLayoutVk.cpp
+++ b/src/dawn/native/vulkan/BindGroupLayoutVk.cpp
@@ -251,7 +251,7 @@
DAWN_TRY(CheckVkSuccess(device->fn.CreateDescriptorSetLayout(device->GetVkDevice(), &createInfo,
nullptr, &*mHandle),
"CreateDescriptorSetLayout"));
- mSpecializations.insert({{}, mHandle});
+ mSpecializations->insert({{}, mHandle});
SetLabelImpl();
@@ -260,8 +260,15 @@
ResultOrError<VkDescriptorSetLayout> BindGroupLayout::GetOrCreateSpecializedHandle(
const Specialization& specialization) {
- if (auto it = mSpecializations.find(specialization); it != mSpecializations.end()) {
- return it->second;
+ if (auto specialized = mSpecializations.ConstUse(
+ [&](auto specializations) -> std::optional<VkDescriptorSetLayout> {
+ if (auto it = specializations->find(specialization); it != specializations->end()) {
+ return it->second;
+ }
+ return std::nullopt;
+ });
+ specialized) {
+ return *specialized;
}
Device* device = ToBackend(GetDevice());
@@ -282,8 +289,14 @@
nullptr, &*specialized),
"CreateDescriptorSetLayout"));
- mSpecializations.insert({specialization, specialized});
- return specialized;
+ return mSpecializations.Use([&](auto specializations) -> ResultOrError<VkDescriptorSetLayout> {
+ auto [it, inserted] = specializations->insert({specialization, specialized});
+ if (!inserted) {
+ device->fn.DestroyDescriptorSetLayout(device->GetVkDevice(), specialized, nullptr);
+ return it->second;
+ }
+ return specialized;
+ });
}
void BindGroupLayout::DestroyImpl(DestroyReason reason) {
@@ -293,10 +306,12 @@
// DescriptorSetLayout aren't used by execution on the GPU and can be deleted at any time,
// so we can destroy mHandle immediately instead of using the FencedDeleter.
- for (auto& [_, handle] : mSpecializations) {
- device->fn.DestroyDescriptorSetLayout(device->GetVkDevice(), handle, nullptr);
- }
- mSpecializations.clear();
+ mSpecializations.Use([&](auto specializations) {
+ for (auto& [_, handle] : *specializations) {
+ device->fn.DestroyDescriptorSetLayout(device->GetVkDevice(), handle, nullptr);
+ }
+ specializations->clear();
+ });
// Handled in the loop above already.
mHandle = VK_NULL_HANDLE;
diff --git a/src/dawn/native/vulkan/BindGroupLayoutVk.h b/src/dawn/native/vulkan/BindGroupLayoutVk.h
index d61a813..1e164e6 100644
--- a/src/dawn/native/vulkan/BindGroupLayoutVk.h
+++ b/src/dawn/native/vulkan/BindGroupLayoutVk.h
@@ -117,7 +117,7 @@
// Caches VkDescriptorSetLayouts for specializations so that the lifetime guarantees are the
// same as for mHandle. Note that the noop specialization has mHandle cached directly, but
// mHandle is also kept separate for efficiency when creating BindGroups.
- absl::flat_hash_map<Specialization, VkDescriptorSetLayout> mSpecializations;
+ MutexProtected<absl::flat_hash_map<Specialization, VkDescriptorSetLayout>> mSpecializations;
// Maps from indices of texture entries that are paired with static samplers
// to indices of the entries of their respective samplers.
Original Bug Report
heap-use-after-free in dawn::native::vulkan::BindGroupLayout::GetOrCreateSpecializedHandle
VULNERABILITY DETAILS
WebGPU supports compiling compute pipelines asynchronously via the createComputePipelineAsync API. This spawns background task worker threads in ThreadPoolForeg to initialize the pipeline in the backend. During backend layout construction, BindGroupLayout leverages an absl::flat_hash_map named mSpecializations to cache and specialize Vulkan descriptor sets and layouts based on ExternalTexture parameters.
In BindGroupLayout::GetOrCreateSpecializedHandle, the asynchronous background thread invokes find to look up an existing layout for the given specialization [0]. However, there is no synchronization or mutex protecting concurrent accesses to this absl::flat_hash_map.
Concurrently, the front-end JS can trigger operations on the main GPU thread, such as executing queue.submit with alternating ExternalTexture bindings to continuously incur cache misses on newly allocated combinations. Because of the intentional cache misses, GetOrCreateSpecializedHandle executes on the main GPU thread and frequently inserts new items into the same hash map [1].
ResultOrError<VkDescriptorSetLayout> BindGroupLayout::GetOrCreateSpecializedHandle(
const Specialization& specialization) {
if (auto it = mSpecializations.find(specialization); it != mSpecializations.end()) { // [0]
return it->second; // [2]
}
// ...
VkDescriptorSetLayout specialized;
DAWN_TRY(
CheckVkSuccess(device->fn.CreateDescriptorSetLayout(device->GetVkDevice(), &createInfo,
nullptr, &*specialized),
"CreateDescriptorSetLayout"));
mSpecializations.insert({specialization, specialized}); // [1]
return specialized;
}
This unsynchronized insert operation can intermittently trigger an internal target container reallocation rehash, which drops the backing nodes. If the worker thread performs mSpecializations.find() at the same time and attempts to dereference the iterator it->second [2], a data race ensues, leading to heap-use-after-free.
BISECTION
Introduced by Dawn upstream commit [0] which added runtime specialization of pipelines and the absl::flat_hash_map caching mechanism without mutex protection.
This regression was rolled into Chromium in commit [1].
[0] https://dawn.googlesource.com/dawn/+/66c4d93dc91932933c4bd66ff6afba1562d51259 ([YUV AHB] Add runtime specialization of pipelines)
[1] https://chromium.googlesource.com/chromium/src/+/5ba6a05e0af48d7b5a38703d0844fea105c5e1fc (Roll Dawn from b263aff3bd77 to 7ae353ac9aa9 (5 revisions))
VERSION
Chrome Version: HEAD
Operating System: Linux
REPRODUCTION CASE
poc.patch is a simple patch that introduces a 50ms artificial sleep in the GPU process to widen the data race window, ensuring the race is won reliably.
- Apply
poc.patch, then build Chromium with ASan. - Host the
poc.htmlon an HTTP server. - Run Chrome against the PoC.
$ python3 -m http.server
$ ./out/asan/chrome --enable-dawn-features=vulkan_force_static_samplers_for_external_textures "http://localhost:8000/poc.html"
CRASH INFORMATION
Type of crash: GPU process
Crash log: see the attached asan.txt ASan trace.
CREDIT INFORMATION
Reporter credit: Anonymous
- http://localhost:8000/poc.html
- https://chromium.googlesource.com/chromium/src/+/5ba6a05e0af48d7b5a38703d0844fea105c5e1fc
- https://dawn.googlesource.com/dawn/+/66c4d93dc91932933c4bd66ff6afba1562d51259
- https://source.chromium.org/chromium/chromium/src/+/main:third_party/dawn/src/dawn/native/vulkan/BindGroupLayoutVk.cpp;drc=35a1c65f9e2f12819540d824d414ae0109bb1b10;l=263
- https://source.chromium.org/chromium/chromium/src/+/main:third_party/dawn/src/dawn/native/vulkan/BindGroupLayoutVk.cpp;drc=35a1c65f9e2f12819540d824d414ae0109bb1b10;l=264
- https://source.chromium.org/chromium/chromium/src/+/main:third_party/dawn/src/dawn/native/vulkan/BindGroupLayoutVk.cpp;drc=35a1c65f9e2f12819540d824d414ae0109bb1b10;l=285