CVE-2026-17758
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifgpu/command_buffer/service/dawn_caching_interface.cc |
modified |
Files Changed
gpu/command_buffer/service/dawn_caching_interface.ccgpu/command_buffer/service/dawn_caching_interface.h
Patch
From 61cf326ad9e0b8c72c5181b3b27c10f1d25b7c3c Mon Sep 17 00:00:00 2001 From: Lokbondo Kung <[email protected]> Date: Wed, 15 Jul 2026 14:44:33 -0700 Subject: [PATCH] [dawn] Remove deprecated non-spanified CachingInterface APIs. - Moves the Dawn specific APIs to be private and implement Chromium native versions of the APIs so that they can be used with the native base::span and std::string_view types. - This change also updates the implementations of some of the APIs to use base::span::copy_*_from, with an additional explicit checks to ensure that we have enough room for loads. - Corresponding unit tests are also updated. Bug: 503801946 Change-Id: I703739d304d0c5cfbdf1aa9d1d71dd3022482e14 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8094122 Commit-Queue: Kai Ninomiya <[email protected]> Reviewed-by: Kai Ninomiya <[email protected]> Reviewed-by: Sunny Sachanandani <[email protected]> Auto-Submit: Loko Kung <[email protected]> Cr-Commit-Position: refs/heads/main@{#1662877} --- diff --git a/gpu/command_buffer/service/dawn_caching_interface.cc b/gpu/command_buffer/service/dawn_caching_interface.cc index b07958c5..8c9224d 100644 --- a/gpu/command_buffer/service/dawn_caching_interface.cc +++ b/gpu/command_buffer/service/dawn_caching_interface.cc @@ -29,90 +29,72 @@ DawnCachingInterface::~DawnCachingInterface() = default; size_t DawnCachingInterface::FindKey(std::span<const std::byte> key) { - if (memory_cache() == nullptr) { - return 0u; - } std::string_view key_str(reinterpret_cast<const char*>(key.data()), key.size()); - auto entry = memory_cache()->Find(key_str); - if (!entry) { - return 0u; - } - return entry->DataSize(); + return FindKey(key_str); } size_t DawnCachingInterface::LoadData(std::span<const std::byte> key, - std::span<std::byte> dest) { - if (memory_cache() == nullptr) { - return 0u; - } + std::span<std::byte> dst) { std::string_view key_str(reinterpret_cast<const char*>(key.data()), key.size()); - auto entry = memory_cache()->Find(key_str); - if (!entry) { - return 0u; - } - - // Verify that the size being copied out is identical. - DCHECK(dest.size() == entry->DataSize()); - - auto src = entry->Data(); - std::ranges::copy(std::as_bytes(std::span(src)), dest.begin()); - return entry->DataSize(); + // SAFETY: `dst` is provided by the caller who is responsible. + base::span<uint8_t> dst_span = UNSAFE_BUFFERS( + base::span(reinterpret_cast<uint8_t*>(dst.data()), dst.size())); + return LoadData(key_str, dst_span); } void DawnCachingInterface::StoreData(std::span<const std::byte> key, std::span<const std::byte> src) { - if (memory_cache() == nullptr || src.empty()) { - return; - } std::string_view key_str(reinterpret_cast<const char*>(key.data()), key.size()); + // SAFETY: `src` is provided by the caller who is responsible. base::span<const uint8_t> src_span = UNSAFE_BUFFERS( base::span(reinterpret_cast<const uint8_t*>(src.data()), src.size())); - memory_cache()->Store(key_str, src_span); - - // Send the cache entry to be stored on the host-side if applicable. - if (cache_blob_callback_) { - std::string key_str_copy(key_str); - std::string src_str(reinterpret_cast<const char*>(src.data()), src.size()); - cache_blob_callback_.Run(key_str_copy, src_str); - } + return StoreData(key_str, src_span); } -size_t DawnCachingInterface::LoadData(const void* key, - size_t key_size, - void* value_out, - size_t value_size) { +size_t DawnCachingInterface::FindKey(std::string_view key) { if (memory_cache() == nullptr) { return 0u; } - - std::string_view key_str(static_cast<const char*>(key), key_size); - auto entry = memory_cache()->Find(key_str); + auto entry = memory_cache()->Find(key); if (!entry) { return 0u; } - return entry->ReadData(value_out, value_size); + return entry->DataSize(); } -void DawnCachingInterface::StoreData(const void* key, - size_t key_size, - const void* value, - size_t value_size) { - if (memory_cache() == nullptr || value == nullptr || value_size <= 0) { - return; +size_t DawnCachingInterface::LoadData(std::string_view key, + base::span<uint8_t> dst) { + if (memory_cache() == nullptr) { + return 0u; + } + auto entry = memory_cache()->Find(key); + if (!entry) { + return 0u; } - std::string key_str(static_cast<const char*>(key), key_size); - memory_cache()->Store( - key_str, UNSAFE_BUFFERS( - base::span(static_cast<const uint8_t*>(value), value_size))); + auto src = entry->Data(); + if (src.size() <= dst.size()) { + dst.copy_prefix_from(src); + return entry->DataSize(); + } + return 0u; +} + +void DawnCachingInterface::StoreData(std::string_view key, + base::span<const uint8_t> src) { + if (memory_cache() == nullptr || src.empty()) { + return; + } + memory_cache()->Store(key, src); // Send the cache entry to be stored on the host-side if applicable. if (cache_blob_callback_) { - std::string value_str(static_cast<const char*>(value), value_size); - cache_blob_callback_.Run(key_str, value_str); + std::string key_str_copy(key); + std::string src_str(reinterpret_cast<const char*>(src.data()), src.size()); + cache_blob_callback_.Run(key_str_copy, src_str); } } diff --git a/gpu/command_buffer/service/dawn_caching_interface.h b/gpu/command_buffer/service/dawn_caching_interface.h index da1cd79..2dbf669 100644 --- a/gpu/command_buffer/service/dawn_caching_interface.h +++ b/gpu/command_buffer/service/dawn_caching_interface.h @@ -5,6 +5,13 @@ #ifndef GPU_COMMAND_BUFFER_SERVICE_DAWN_CACHING_INTERFACE_H_ #define GPU_COMMAND_BUFFER_SERVICE_DAWN_CACHING_INTERFACE_H_ +// TODO(503801946): Remove this Clang suppression once we remove the old Dawn +// caching APIs that are causing the overload conflicts. +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Woverloaded-virtual" +#endif + #include <dawn/platform/DawnPlatform.h> #include <memory> @@ -38,26 +45,23 @@ ~DawnCachingInterface() override; - size_t FindKey(std::span<const std::byte> key) override; - size_t LoadData(std::span<const std::byte> key, - std::span<std::byte> dest) override; - void StoreData(std::span<const std::byte> key, - std::span<const std::byte> src) override; - - // TODO(503801946): Remove these outdated non-spanified implementations once - // we have migrated to use the one's above. - size_t LoadData(const void* key, - size_t key_size, - void* value_out, - size_t value_size) override; - void StoreData(const void* key, - size_t key_size, - const void* value, - size_t value_size) override; + // Chromium versions using std::string_view and base::span of the Dawn APIs + // that should be used in Chromium usages. + size_t FindKey(std::string_view key); + size_t LoadData(std::string_view key, base::span<uint8_t> dst); + void StoreData(std::string_view key, base::span<const uint8_t> src); private: friend class DawnCachingInterfaceFactory;
Regression Test / PoC
diff --git a/gpu/command_buffer/service/dawn_caching_interface_unittest.cc b/gpu/command_buffer/service/dawn_caching_interface_unittest.cc
index 806e26d..68d47c0e 100644
--- a/gpu/command_buffer/service/dawn_caching_interface_unittest.cc
+++ b/gpu/command_buffer/service/dawn_caching_interface_unittest.cc
@@ -8,6 +8,7 @@
#include <string_view>
#include "base/compiler_specific.h"
+#include "base/containers/span.h"
#include "base/files/scoped_temp_dir.h"
#include "base/test/scoped_feature_list.h"
#include "gpu/command_buffer/service/gpu_persistent_cache.h"
@@ -25,7 +26,6 @@
protected:
static constexpr std::string_view kKey = "cache key";
static constexpr std::string_view kData = "some data";
- static constexpr size_t kKeySize = kKey.size();
static constexpr size_t kDataSize = kData.size();
static constexpr gpu::GpuDiskCacheDawnWebGPUHandle kDawnWebGPUHandle =
gpu::GpuDiskCacheDawnWebGPUHandle(1);
@@ -39,72 +39,65 @@
TEST_F(DawnCachingInterfaceTest, LoadNonexistentSize) {
auto dawn_caching_interface = factory_.CreateInstance(handle_);
- EXPECT_EQ(
- 0u, dawn_caching_interface->LoadData(kKey.data(), kKeySize, nullptr, 0));
+ EXPECT_EQ(0u, dawn_caching_interface->FindKey(kKey));
}
TEST_F(DawnCachingInterfaceTest, StoreThenLoadSameInterface) {
auto dawn_caching_interface = factory_.CreateInstance(handle_);
- dawn_caching_interface->StoreData(kKey.data(), kKeySize, kData.data(),
- kDataSize);
+ dawn_caching_interface->StoreData(kKey, base::as_byte_span(kData));
char buffer[kDataSize];
- EXPECT_EQ(kDataSize, dawn_caching_interface->LoadData(kKey.data(), kKeySize,
- nullptr, 0));
- EXPECT_EQ(kDataSize, dawn_caching_interface->LoadData(kKey.data(), kKeySize,
- buffer, kDataSize));
+ EXPECT_EQ(kDataSize, dawn_caching_interface->FindKey(kKey));
+ EXPECT_EQ(kDataSize, dawn_caching_interface->LoadData(
+ kKey, base::as_writable_byte_span(buffer)));
UNSAFE_TODO(EXPECT_EQ(0, memcmp(buffer, kData.data(), kDataSize)));
}
TEST_F(DawnCachingInterfaceTest, LoadPartialData) {
auto dawn_caching_interface = factory_.CreateInstance(handle_);
- dawn_caching_interface->StoreData(kKey.data(), kKeySize, kData.data(),
- kDataSize);
+ dawn_caching_interface->StoreData(kKey, base::as_byte_span(kData));
static constexpr size_t kPartialSize = kDataSize / 2;
char buffer[kPartialSize];
// Should return 0 because of size mismatch.
- EXPECT_EQ(0u, dawn_caching_interface->LoadData(kKey.data(), kKeySize, buffer,
- kPartialSize));
+ EXPECT_EQ(0u, dawn_caching_interface->LoadData(
+ kKey, base::as_writable_byte_span(buffer)));
}
TEST_F(DawnCachingInterfaceTest, LoadLargerBuffer) {
auto dawn_caching_interface = factory_.CreateInstance(handle_);
- dawn_caching_interface->StoreData(kKey.data(), kKeySize, kData.data(),
- kDataSize);
+ dawn_caching_interface->StoreData(kKey, base::as_byte_span(kData));
static constexpr size_t kLargerSize = kDataSize * 2;
char buffer[kLargerSize];
// Should return kDataSize and only copy kDataSize bytes.
- EXPECT_EQ(kDataSize, dawn_caching_interface->LoadData(kKey.data(), kKeySize,
- buffer, kLargerSize));
+ EXPECT_EQ(kDataSize, dawn_caching_interface->LoadData(
+ kKey, base::as_writable_byte_span(buffer)));
UNSAFE_TODO(EXPECT_EQ(0, memcmp(buffer, kData.data(), kDataSize)));
}
TEST_F(DawnCachingInterfaceTest, StoreThenLoadSameHandle) {
auto store_interface = factory_.CreateInstance(handle_);
- store_interface->StoreData(kKey.data(), kKeySize, kData.data(), kDataSize);
+ store_interface->StoreData(kKey, base::as_byte_span(kData));
auto load_interface = factory_.CreateInstance(handle_);
char buffer[kDataSize];
- EXPECT_EQ(kDataSize,
- load_interface->LoadData(kKey.data(), kKeySize, nullptr, 0));
- EXPECT_EQ(kDataSize,
- load_interface->LoadData(kKey.data(), kKeySize, buffer, kDataSize));
+ EXPECT_EQ(kDataSize, load_interface->FindKey(kKey));
+ EXPECT_EQ(kDataSize, load_interface->LoadData(
+ kKey, base::as_writable_byte_span(buffer)));
UNSAFE_TODO(EXPECT_EQ(0, memcmp(buffer, kData.data(), kDataSize)));
}
TEST_F(DawnCachingInterfaceTest, StoreDestroyThenLoadSameHandle) {
auto store_interface = factory_.CreateInstance(handle_);
- store_interface->StoreData(kKey.data(), kKeySize, kData.data(), kDataSize);
+ store_interface->StoreData(kKey, base::as_byte_span(kData));
store_interface.reset();
auto load_interface = factory_.CreateInstance(handle_);
char buffer[kDataSize];
- EXPECT_EQ(kDataSize,
- load_interface->LoadData(kKey.data(), kKeySize, nullptr, 0));
- EXPECT_EQ(kDataSize,
- load_interface->LoadData(kKey.data(), kKeySize, buffer, kDataSize));
+ EXPECT_EQ(kDataSize, load_interface->FindKey(kKey));
+ EXPECT_EQ(kDataSize, load_interface->LoadData(
+ kKey, base::as_writable_byte_span(buffer)));
UNSAFE_TODO(EXPECT_EQ(0, memcmp(buffer, kData.data(), kDataSize)));
}
@@ -112,20 +105,20 @@
// use a new in-memory cache.
TEST_F(DawnCachingInterfaceTest, StoreReleaseThenLoad) {
auto store_interface = factory_.CreateInstance(handle_);
- store_interface->StoreData(kKey.data(), kKeySize, kData.data(), kDataSize);
+ store_interface->StoreData(kKey, base::as_byte_span(kData));
store_interface.reset();
factory_.ReleaseHandle(handle_);
auto load_interface = factory_.CreateInstance(handle_);
- EXPECT_EQ(0u, load_interface->LoadData(kKey.data(), kKeySize, nullptr, 0));
+ EXPECT_EQ(0u, load_interface->FindKey(kKey));
}
TEST_F(DawnCachingInterfaceTest, IncognitoCachesDoNotShare) {
auto interface_1 = factory_.CreateInstance();
- interface_1->StoreData(kKey.data(), kKeySize, kData.data(), kDataSize);
+ interface_1->StoreData(kKey, base::as_byte_span(kData));
auto interface_2 = factory_.CreateInstance();
- EXPECT_EQ(0u, interface_2->LoadData(kKey.data(), kKeySize, nullptr, 0));
+ EXPECT_EQ(0u, interface_2->FindKey(kKey));
}
TEST_F(DawnCachingInterfaceTest, UnableToCreateBackend) {
@@ -136,16 +129,13 @@
// Without an actual backend, all loads and stores should do nothing.
{
auto incongnito_interface = factory.CreateInstance();
- incongnito_interface->StoreData(kKey.data(), kKeySize, kData.data(),
- kDataSize);
- EXPECT_EQ(
- 0u, incongnito_interface->LoadData(kKey.data(), kKeySize, nullptr, 0));
+ incongnito_interface->StoreData(kKey, base::as_byte_span(kData));
+ EXPECT_EQ(0u, incongnito_interface->FindKey(kKey));
}
{
auto handle_interface = factory.CreateInstance(handle_);
- handle_interface->StoreData(kKey.data(), kKeySize, kData.data(), kDataSize);
- EXPECT_EQ(0u,
- handle_interface->LoadData(kKey.data(), kKeySize, nullptr, 0));
+ handle_interface->StoreData(kKey, base::as_byte_span(kData));
+ EXPECT_EQ(0u, handle_interface->FindKey(kKey));
}
}
@@ -158,8 +148,7 @@
EXPECT_CALL(decoder_client_mock_,
CacheBlob(gpu::GpuDiskCacheType::kDawnWebGPU, std::string(kKey),
std::string(kData)));
- dawn_caching_interface->StoreData(kKey.data(), kKeySize, kData.data(),
- kDataSize);
+ dawn_caching_interface->StoreData(kKey, base::as_byte_span(kData));
}
TEST_F(DawnCachingInterfaceTest, TestMaxSizeEviction) {
@@ -179,11 +168,11 @@
[]() { return base::MakeRefCounted<MemoryCache>(kCacheSize); }));
auto interface = factory.CreateInstance();
- interface->StoreData(kKey1.data(), kKeySize, kData1.data(), kDataSize);
- interface->StoreData(kKey2.data(), kKeySize, kData2.data(), kDataSize);
+ interface->StoreData(kKey1, base::as_byte_span(kData1));
+ interface->StoreData(kKey2, base::as_byte_span(kData2));
- EXPECT_EQ(0u, interface->LoadData(kKey1.data(), 1u, nullptr, 0));
- EXPECT_EQ(kDataSize, interface->LoadData(kKey2.data(), 1u, nullptr, 0));
+ EXPECT_EQ(0u, interface->FindKey(kKey1));
+ EXPECT_EQ(kDataSize, interface->FindKey(kKey2));
}
TEST_F(DawnCachingInterfaceTest, TestLruEviction) {
@@ -209,14 +198,14 @@
// Even though Key1 was stored first, because we loaded it once, Key2 should
// be the one to be evicted when Key3 is added.
auto interface = factory.CreateInstance();
- interface->StoreData(kKey1.data(), kKeySize, kData1.data(), kDataSize);
- interface->StoreData(kKey2.data(), kKeySize, kData2.data(), kDataSize);
- EXPECT_EQ(kDataSize, interface->LoadData(kKey1.data(), 1u, nullptr, 0));
- interface->StoreData(kKey3.data(), kKeySize, kData3.data(), kDataSize);
+ interface->StoreData(kKey1, base::as_byte_span(kData1));
+ interface->StoreData(kKey2, base::as_byte_span(kData2));
+ EXPECT_EQ(kDataSize, interface->FindKey(kKey1));
+ interface->StoreData(kKey3, base::as_byte_span(kData3));
- EXPECT_EQ(kDataSize, interface->LoadData(kKey1.data(), 1u, nullptr, 0));
- EXPECT_EQ(0u, interface->LoadData(kKey2.data(), 1u, nullptr, 0));
- EXPECT_EQ(kDataSize, interface->LoadData(kKey3.data(), 1u, nullptr, 0));
+ EXPECT_EQ(kDataSize, interface->FindKey(kKey1));
+ EXPECT_EQ(0u, interface->FindKey(kKey2));
+ EXPECT_EQ(kDataSize, interface->FindKey(kKey3));
}
// Entries that are too large for the size of the cache are not cached and do
@@ -224,7 +213,6 @@
TEST_F(DawnCachingInterfaceTest, TestVeryLargeEntrySize) {
static constexpr std::string_view kSmall = "1";
static constexpr std::string_view kLarge = "11111";
- static constexpr size_t kSmallSize = kSmall.size();
static constexpr size_t kLargeSize = kLarge.size();
static constexpr size_t kCacheSize = kLargeSize - 1u;
@@ -235,20 +223,20 @@
{
// When the key is larger than the cache size but the value is not, caching
// fails.
- interface->StoreData(kLarge.data(), kLargeSize, kSmall.data(), kSmallSize);
- EXPECT_EQ(0u, interface->LoadData(kLarge.data(), kLargeSize, nullptr, 0));
+ interface->StoreData(kLarge, base::as_byte_span(kSmall));
+ EXPECT_EQ(0u, interface->FindKey(kLarge));
}
{
// When the key is smaller than the cache size, but the value is not,
// caching fails.
- interface->StoreData(kSmall.data(), kSmallSize, kLarge.data(), kLargeSize);
- EXPECT_EQ(0u, interface->LoadData(kSmall.data(), kSmallSize, nullptr, 0));
+ interface->StoreData(kSmall, base::as_byte_span(kLarge));
+ EXPECT_EQ(0u, interface->FindKey(kSmall));
}
{
// When the both the key and the value is larger than the cache size,
// caching fails.
- interface->StoreData(kLarge.data(), kLargeSize, kLarge.data(), kLargeSize);
- EXPECT_EQ(0u, interface->LoadData(kLarge.data(), kLargeSize, nullptr, 0));
+ interface->StoreData(kLarge, base::as_byte_span(kLarge));
+ EXPECT_EQ(0u, interface->FindKey(kLarge));
}
}
@@ -268,11 +256,11 @@
auto interfaces = {factory.CreateInstance(kDawnGraphiteHandle),
factory.CreateInstance(kDawnWebGPUHandle)};
for (auto& interface : interfaces) {
- interface->StoreData(kKey1.data(), kKeySize, kData1.data(), kDataSize);
- EXPECT_EQ(kDataSize, interface->LoadData(kKey1.data(), 1u, nullptr, 0));
+ interface->StoreData(kKey1, base::as_byte_span(kData1));
+ EXPECT_EQ(kDataSize, interface->FindKey(kKey1));
factory.PurgeMemory(base::MEMORY_PRESSURE_LEVEL_CRITICAL);
- EXPECT_EQ(0u, interface->LoadData(kKey1.data(), 1u, nullptr, 0));
+ EXPECT_EQ(0u, interface->FindKey(kKey1));
}
}
@@ -293,19 +281,19 @@
auto interfaces = {factory.CreateInstance(kDawnGraphiteHandle),
factory.CreateInstance(kDawnWebGPUHandle)};
for (auto& interface : interfaces) {
- interface->StoreData(kKey1.data(), kKeySize, kData1.data(), kDataSize);
- EXPECT_EQ(kDataSize, interface->LoadData(kKey1.data(), 1u, nullptr, 0));
+ interface->StoreData(kKey1, base::as_byte_span(kData1));
+ EXPECT_EQ(kDataSize, interface->FindKey(kKey1));
// Moderate memory pressure is ignored
factory.PurgeMemory(base::MEMORY_PRESSURE_LEVEL_MODERATE);
- EXPECT_EQ(kDataSize, interface->LoadData(kKey1.data(), 1u, nullptr, 0));
+ EXPECT_EQ(kDataSize, interface->FindKey(kKey1));
// But not critical, except on Android
factory.PurgeMemory(base::MEMORY_PRESSURE_LEVEL_CRITICAL);
#if BUILDFLAG(IS_ANDROID)
- EXPECT_EQ(kDataSize, interface->LoadData(kKey1.data(), 1u, nullptr, 0));
+ EXPECT_EQ(kDataSize, interface->FindKey(kKey1));
#else
- EXPECT_EQ(0u, interface->LoadData(kKey1.data(), 1u, nullptr, 0));
+ EXPECT_EQ(0u, interface->FindKey(kKey1));
#endif
}
}
diff --git a/gpu/command_buffer/service/gpu_persistent_cache_unittest.cc b/gpu/command_buffer/service/gpu_persistent_cache_unittest.cc
index a2c2a4b65..3d2913c 100644
--- a/gpu/command_buffer/service/gpu_persistent_cache_unittest.cc
+++ b/gpu/command_buffer/service/gpu_persistent_cache_unittest.cc
@@ -6,6 +6,7 @@
#include "base/barrier_closure.h"
#include "base/containers/heap_array.h"
+#include "base/containers/span.h"
#include "base/files/scoped_temp_dir.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
@@ -73,13 +74,10 @@
const std::string value = "my_value";
// StoreData() won't do anything but also won't crash.
- cache_with_no_memory_cache->StoreData(key.c_str(), key.size(), value.c_str(),
- value.size());
+ cache_with_no_memory_cache->StoreData(key, base::as_byte_span(value));
... (truncated)
Original Bug Report
GPU process heap-buffer-overflow write in Dawn BlobCache load via Chromium MemoryCache size race
VULNERABILITY DETAILS
Summary
Dawn’s BlobCache::LoadInternal() uses a two-call cache loading API: the first callback call queries the value size, then Dawn allocates a buffer of exactly that size, and the second callback call fills the buffer. The code only checks whether the second returned size changed after the second callback has already written into the buffer.
In Chromium’s WebGPU cache backend, DawnCachingInterface::LoadData() forwards the requested buffer size into MemoryCacheEntry::ReadData(), but ReadData() only enforces equal sizes with DCHECK. In release builds it copies the complete stored value into the caller’s pointer even if the caller provided a smaller value_size.
If the same cache key is replaced with a larger value between Dawn’s first and second cache load calls, the second load can copy the larger Chromium cache entry into Dawn’s smaller heap allocation.
Root cause analysis
// src/third_party/dawn/src/dawn/native/BlobCache.cpp
BlobCache::BlobCache(const dawn::native::DawnCacheDeviceDescriptor& desc, bool enableHashValidation)
: mHashValidation(enableHashValidation),
mLoadFunction(desc.loadDataFunction),
mStoreFunction(desc.storeDataFunction),
mFunctionUserdata(desc.functionUserdata) {}
ResultOrError<Blob> BlobCache::Load(const CacheKey& key) {
return LoadInternal(key);
}
void BlobCache::Store(const CacheKey& key, size_t valueSize, const void* value) {
StoreInternal(key, valueSize, value);
}
void BlobCache::Store(const CacheKey& key, const Blob& value) {
Store(key, value.Size(), value.Data());
}
Blob BlobCache::GenerateActualStoredBlobForTesting(size_t valueSize, const void* value) {
if (!mHashValidation) {
Blob blob = CreateBlob(valueSize);
memcpy(blob.Data(), value, valueSize);
return blob;
}
return details::GenerateHashPrefixedPayload(valueSize, value);
}
void BlobCache::StoreInternal(const CacheKey& key, size_t valueSize, const void* value) {
DAWN_ASSERT(ValidateCacheKey(key));
DAWN_ASSERT(value != nullptr);
DAWN_ASSERT(valueSize > 0);
if (mStoreFunction == nullptr) {
return;
}
// Call the actual store function for actual stored bytes.
if (!mHashValidation) {
mStoreFunction(key.data(), key.size(), value, valueSize, mFunctionUserdata);
} else {
Blob actualStoredBlob = details::GenerateHashPrefixedPayload(valueSize, value);
mStoreFunction(key.data(), key.size(), actualStoredBlob.Data(), actualStoredBlob.Size(),
mFunctionUserdata);
}
}
ResultOrError<Blob> BlobCache::LoadInternal(const CacheKey& key) {
DAWN_ASSERT(ValidateCacheKey(key));
if (mLoadFunction == nullptr) {
return Blob();
}
const size_t expectedSize =
mLoadFunction(key.data(), key.size(), nullptr, 0, mFunctionUserdata);
// Non-zero size indicates cache hit
if (expectedSize > 0) {
// Load bytes from cache.
uint8_t* buffer = new uint8_t[expectedSize];
const size_t actualSize =
mLoadFunction(key.data(), key.size(), buffer, expectedSize, mFunctionUserdata);
// TODO(crbug.com/469351711): If `mLoadFunction` returns a different size on the second call
// (due to external cache eviction, I/O errors, or timeouts), treat it as a cache miss. The
// blob cache API should be updated to a single `mLoadFunction` call in the future.
if (expectedSize != actualSize) {
delete[] buffer;
return Blob();
}
if (!mHashValidation) {
return Blob::UnsafeCreateWithDeleter(buffer, actualSize, [=]() { delete[] buffer; });
}
return details::CheckAndUnpackHashPrefixedPayload(buffer, expectedSize);
}
return Blob();
}
The first bug is the non-atomic size-query/load pattern. expectedSize comes from a first callback invocation, but the actual bytes are fetched by a second callback invocation with no cache entry pinning or Dawn-side lock. If the backing cache returns a different, larger value during the second call, Dawn has already allocated only expectedSize bytes. The mismatch check at expectedSize != actualSize is conceptually correct for detecting stale cache data, but it is placed after the write to buffer, so it cannot prevent an oversized callee from corrupting the heap.
// src/gpu/command_buffer/service/dawn_caching_interface.cc
size_t DawnCachingInterface::LoadData(const void* key,
size_t key_size,
void* value_out,
size_t value_size) {
if (memory_cache() == nullptr) {
return 0u;
}
std::string_view key_str(static_cast<const char*>(key), key_size);
auto entry = memory_cache()->Find(key_str);
if (!entry) {
return 0u;
}
return entry->ReadData(value_out, value_size);
}
void DawnCachingInterface::StoreData(const void* key,
size_t key_size,
const void* value,
size_t value_size) {
if (memory_cache() == nullptr || value == nullptr || value_size <= 0) {
return;
}
std::string key_str(static_cast<const char*>(key), key_size);
memory_cache()->Store(
key_str, UNSAFE_BUFFERS(
base::span(static_cast<const uint8_t*>(value), value_size)));
// Send the cache entry to be stored on the host-side if applicable.
if (cache_blob_callback_) {
std::string value_str(static_cast<const char*>(value), value_size);
cache_blob_callback_.Run(key_str, value_str);
}
}
This is the Chromium WebGPU cache adapter used by Dawn. It does not preserve the MemoryCacheEntry from Dawn’s first size query across Dawn’s second load call. Each LoadData() call performs a fresh memory_cache()->Find(key_str). Therefore a concurrent or interleaved StoreData() for the same key can cause the first Dawn load to observe entry A and the second Dawn load to observe larger entry B. StoreData() also replaces existing keys through MemoryCache::Store().
// src/gpu/command_buffer/service/memory_cache.cc
size_t MemoryCacheEntry::DataSize() const {
return data_.size();
}
size_t MemoryCacheEntry::ReadData(void* value_out, size_t value_size) const {
// First handle "peek" case where use is trying to get the size of the entry.
if (value_out == nullptr && value_size == 0) {
return DataSize();
}
// Otherwise, verify that the size that is being copied out is identical.
DCHECK(value_size == DataSize());
std::copy(data_.begin(), data_.end(), static_cast<uint8_t*>(value_out));
return value_size;
}
base::span<const uint8_t> MemoryCacheEntry::Data() const {
return data_;
}
This is the sink. ReadData() treats (nullptr, 0) as a size query, but for the actual read it only uses DCHECK(value_size == DataSize()). In release builds, the DCHECK is removed and std::copy(data_.begin(), data_.end(), value_out) copies the whole cached value. If Dawn passes a buffer of size expectedSize but the fresh cache entry has DataSize() > expectedSize, this function writes past Dawn’s allocation.
// src/gpu/command_buffer/service/memory_cache.cc
scoped_refptr<MemoryCacheEntry> MemoryCache::Find(std::string_view key) {
// Because we are tracking LRU, even loads modify internal state so mutex is
// required.
base::AutoLock lock(mutex_);
auto it = entries_.find(key);
if (it == entries_.end()) {
return nullptr;
}
if (!cache_hit_trace_event_.empty()) {
TRACE_EVENT0("gpu", cache_hit_trace_event_.c_str());
}
// Even if this was just a "peek" operation to get size, the entry was
// accessed so move it to the back of the eviction queue.
scoped_refptr<MemoryCacheEntry>& entry = *it;
entry->RemoveFromList();
lru_.Append(entry.get());
return entry;
}
scoped_refptr<MemoryCacheEntry> MemoryCache::Store(
std::string_view key,
base::span<const uint8_t> data) {
base::AutoLock lock(mutex_);
EvictEntry(key);
if (!CanFitMemoryCacheEntry(key.size() + data.size())) {
return nullptr;
}
auto entry = base::MakeRefCounted<MemoryCacheEntry>(key, data);
InsertEntry(entry);
return entry;
}
scoped_refptr<MemoryCacheEntry> MemoryCache::Store(
std::string_view key,
base::HeapArray<uint8_t> data) {
base::AutoLock lock(mutex_);
EvictEntry(key);
if (!CanFitMemoryCacheEntry(key.size() + data.size())) {
return nullptr;
}
auto entry = base::MakeRefCounted<MemoryCacheEntry>(key, std::move(data));
InsertEntry(entry);
return entry;
}
MemoryCache has a mutex for individual map operations, but the mutex does not cover Dawn’s whole two-call load sequence. The first Find() can return the old entry size, then Store() can evict that key and insert a larger entry, and then the second Find() can return the new entry. The developer mistake is treating two independent cache operations as if they formed one stable transaction and relying on a debug-only size assertion at the sink.
// src/gpu/config/gpu_finch_features.cc
// Enable WebGPU on gpu service side only. This is used with origin trial and
// enabled by default on supported platforms.
#if BUILDFLAG(IS_APPLE) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_CHROMEOS) || \
BUILDFLAG(IS_ANDROID) || BUILDFLAG(USE_WEBGPU_ON_VULKAN_VIA_GL_INTEROP)
#define WEBGPU_ENABLED base::FEATURE_ENABLED_BY_DEFAULT
#else
#define WEBGPU_ENABLED base::FEATURE_DISABLED_BY_DEFAULT
#endif
BASE_FEATURE(kWebGPUService, WEBGPU_ENABLED);
BASE_FEATURE(kWebGPUBlobCache, WEBGPU_ENABLED);
#undef WEBGPU_ENABLED
WebGPU blob cache is enabled by default on supported platforms. A normal web page can reach WebGPU device and pipeline creation APIs when WebGPU is available. This makes the cache path browser-exposed, although the exact same-key size growth condition still needs a reliable page-controlled trigger.
// src/gpu/command_buffer/service/webgpu_decoder_impl.cc
dawn_platform_(new DawnPlatform(
base::FeatureList::IsEnabled(features::kWebGPUBlobCache)
? std::move(dawn_caching_interface)
: nullptr,
/*progress_reporter=*/nullptr,
/*uma_prefix=*/"GPU.WebGPU.",
/*record_cache_count_uma=*/false)),
// src/gpu/command_buffer/service/webgpu_decoder_impl.cc
// Dawn caching isolation key information needs to be passed per device. If an
// isolation key is empty, we do not pass this extra descriptor, and disable
// the blob cache via toggles above.
wgpu::DawnCacheDeviceDescriptor dawn_cache;
if (!isolation_key_->empty()) {
dawn_cache.isolationKey = isolation_key_->c_str();
ChainStruct(desc, &dawn_cache);
}
Chrome’s WebGPU decoder passes Chromium’s Dawn cache implementation into DawnPlatform and enables per-device Dawn caching when the isolation key is present. The source-to-sink path is therefore: page WebGPU operation -> Dawn cache lookup -> DawnCachingInterface::LoadData() -> MemoryCacheEntry::ReadData() -> OOB write into Dawn’s heap buffer if the same key grew between the two load calls.
// src/third_party/dawn/src/dawn/native/PipelineCache.cpp
MaybeError PipelineCacheBase::Flush() {
// Try to write the data out to the persistent cache.
Blob blob;
DAWN_TRY(SerializeToBlobImpl(&blob));
if (blob.Size() > 0) {
// Using a simple heuristic to decide whether to write out the blob right now. May need
// smarter tracking when we are dealing with monolithic caches.
mCache->Store(mKey, blob);
}
return {};
}
MaybeError PipelineCacheBase::DidCompilePipeline() {
DAWN_ASSERT(mInitialized);
if (mStoreOnIdle) {
// Assume pipeline cache was modified by compiling a pipeline. It will be stored in
// BlobCache at some later point in StoreOnIdle() if necessary.
mNeedsStore.store(true, std::memory_order_relaxed);
} else {
// TODO(dawn:549): Flush is currently synchronously happening on the same thread as pipeline
// compilation, but it's perhaps deferrable.
if (!CacheHit()) {
return Flush();
}
}
return {};
}
Dawn stores compiled shader and pipeline data in BlobCache. For a same-key size race, the important requirement is that the same cache key can be stored again with a larger value. The HTML PoC triggers same-key cache growth and reaches the vulnerable DawnCachingInterface::LoadData() to MemoryCacheEntry::ReadData() copy path when Chrome runs with a Vulkan-backed WebGPU/Dawn backend.
VERSION
Chromium revision: 2756f8ee1f15554eacec5d4ed336abd87101a446
Dawn revision: 254f203ab3198c692e78162947e5e1abbedbc92b
Dawn commit that introduced the vulnerable two-call load pattern according to blame:
86578e2fc03 - BlobCache::LoadInternal() lines 139-151.
Related Dawn commit that added the late mismatch check but still checks after the write:
fbfb4497e03 - BlobCache::LoadInternal() lines 152-158.
Chromium commit related to the sink according to blame:
d9d773bff86705 - MemoryCacheEntry::ReadData() copies data_.begin() to value_out after debug-only size validation.
Chromium commit related to the WebGPU cache adapter according to blame:
94d888ff461204 - DawnCachingInterface::LoadData() calls memory_cache()->Find() and entry->ReadData(value_out, value_size).
REPRODUCTION CASE
Host/reproduction environment:
OS: Ubuntu 24.04.3 LTS
GPU render node: /dev/dri/renderD128
GPU card node: /dev/dri/card1
Local build configuration:
is_asan = true
is_debug = false
dcheck_always_on = false
symbol_level = 2
Apply poc.patch for easier reproduction:
diff --git a/gpu/command_buffer/service/dawn_caching_interface.cc b/gpu/command_buffer/service/dawn_caching_interface.cc
index 2d470a9312d87..ff5c2ece7e26f 100644
--- a/gpu/command_buffer/service/dawn_caching_interface.cc
+++ b/gpu/command_buffer/service/dawn_caching_interface.cc
@@ -13,6 +13,8 @@
#include "base/memory/ptr_util.h"
#include "base/strings/stringprintf.h"
#include "base/task/single_thread_task_runner.h"
+#include "base/threading/platform_thread.h"
+#include "base/time/time.h"
#include "base/trace_event/memory_dump_manager.h"
#include "base/trace_event/memory_dump_request_args.h"
#include "base/trace_event/trace_event.h"
@@ -21,6 +23,12 @@
namespace gpu::webgpu {
+namespace {
+
+constexpr base::TimeDelta kBlobCacheOobPeekDelay = base::Milliseconds(2);
+
+} // namespace
+
DawnCachingInterface::DawnCachingInterface(scoped_refptr<MemoryCache> backend,
CacheBlobCallback callback)
: memory_cache_backend_(std::move(backend)),
@@ -41,7 +49,11 @@ size_t DawnCachingInterface::LoadData(const void* key,
if (!entry) {
return 0u;
}
- return entry->ReadData(value_out, value_size);
+ const size_t returned_size = entry->ReadData(value_out, value_size);
+ if (value_out == nullptr && value_size == 0) {
+ base::PlatformThread::Sleep(kBlobCacheOobPeekDelay);
+ }
+ return returned_size;
}
void DawnCachingInterface::StoreData(const void* key,
Build:
autoninja -C out/asan chrome
Serve the HTML PoC:
python3 -m http.server 8765 --bind 127.0.0.1
Use a GPU-backed display. Because my local display session was not using DRI3 with the render node, I created a separate GPU-backed display for reproduction:
Xvnc :2 -ac -hw3d -drinode /dev/dri/renderD128 -geometry 1280x720 -depth 24 -noWebsocket -rfbport 5992 -localhost -SecurityTypes None
The display was verified to use the Intel Mesa renderer:
DISPLAY=:2 glxinfo -B
Execute:
DISPLAY=:2 \
/home/slave/chromium-bug-bounty/chromium/src/out/asan/chrome \
--no-sandbox \
--enable-features=Vulkan \
'http://127.0.0.1:8765/chrome-webgpu-blob-cache-race-poc.html?mode=samekey&workers=4&workerIterations=1&complexity=64&hitDelayMs=100' > ./asan.log 2>&1
Result:
...
Warning: maxDynamicStorageBuffersPerPipelineLayout artificially reduced from 500000 to 16 to fit dynamic offset allocation limit.
Warning: maxDynamicUniformBuffersPerPipelineLayout artificially reduced from 500000 to 16 to fit dynamic offset allocation limit.
Warning: maxDynamicStorageBuffersPerPipelineLayout artificially reduced from 500000 to 16 to fit dynamic offset allocation limit.
=================================================================
==250513==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x77a1b624e54c at pc 0x584ad9997908 bp 0x758150fc9d70 sp 0x758150fc9530
WRITE of size 11900 at 0x77a1b624e54c thread T58 (ThreadPoolForeg)
#0 0x584ad9997907 in __asan_memmove (/home/slave/chromium-bug-bounty/chromium/src/out/asan/chrome+0x10fd1907) (BuildId: 8515f221df06fcdf)
#1 0x584afb87a5ca in gpu::MemoryCacheEntry::ReadData(void*, unsigned long) const gen/third_party/libc++/src/include/__string/constexpr_c_functions.h:227:5
#2 0x584afbb00245 in gpu::webgpu::DawnCachingInterface::LoadData(void const*, unsigned long, void*, unsigned long) gpu/command_buffer/service/dawn_caching_interface.cc:52:39
#3 0x584adb9521e7 in dawn::native::BlobCache::LoadInternal(dawn::native::CacheKey const&) third_party/dawn/src/dawn/native/BlobCache.cpp:151:13
#4 0x584adbae09b7 in dawn::native::PipelineCacheBase::Initialize() third_party/dawn/src/dawn/native/PipelineCache.cpp:40:31
#5 0x584adbdff6d8 in dawn::native::vulkan::PipelineCache::Initialize() third_party/dawn/src/dawn/native/vulkan/PipelineCacheVk.cpp:115:36
#6 0x584adbdff58b in dawn::native::vulkan::PipelineCache::Create(dawn::native::vulkan::Device*, dawn::native::CacheKey const&) third_party/dawn/src/dawn/native/vulkan/PipelineCacheVk.cpp:45:12
#7 0x584adbdb62d5 in dawn::native::vulkan::Device::GetOrCreatePipelineCacheImpl(dawn::native::CacheKey const&) third_party/dawn/src/dawn/native/vulkan/DeviceVk.cpp:282:12
#8 0x584adb9eb018 in dawn::native::DeviceBase::GetOrCreatePipelineCache(dawn::native::CacheKey const&) third_party/dawn/src/dawn/native/Device.cpp:1175:12
#9 0x584adbd9fa76 in dawn::native::vulkan::ComputePipeline::InitializeSpecialization(dawn::native::vulkan::CommonPipelineSpecialization const&, bool) third_party/dawn/src/dawn/native/vulkan/ComputePipelineVk.cpp:190:55
#10 0x584adbd9eb67 in dawn::native::vulkan::ComputePipeline::InitializeImpl() third_party/dawn/src/dawn/native/vulkan/ComputePipelineVk.cpp:70:24
#11 0x584adb9ca83c in dawn::native::ComputePipelineBase::InitializeWithShaders() third_party/dawn/src/dawn/native/ComputePipeline.cpp:76:37
#12 0x584adbad6677 in dawn::native::PipelineBase::Initialize(std::__Cr::optional<dawn::native::PerStage<dawn::native::APIRef<dawn::native::ShaderModuleBase>>>) third_party/dawn/src/dawn/native/Pipeline.cpp:445:22
#13 0x584adb9d6abe in dawn::native::CreatePipelineAsyncEvent<dawn::native::ComputePipelineBase, WGPUCreateComputePipelineAsyncCallbackInfo>::InitializeImpl(bool) third_party/dawn/src/dawn/native/CreatePipelineAsyncEvent.cpp:152:33
#14 0x584adb8ec75b in dawn::native::AsyncTask::Run() gen/third_party/libc++/src/include/__functional/function.h:502:12
#15 0x584adb8ed7e6 in dawn::native::AsyncTaskManager::RunTask(void*) third_party/dawn/src/dawn/native/AsyncTask.cpp:169:16
#16 0x584afb27ec33 in gpu::webgpu::(anonymous namespace)::AsyncWorkerTaskPool::RunWorkerTask(void (*)(void*), void*, gl::ProgressReporter*, scoped_refptr<gpu::webgpu::(anonymous namespace)::AsyncWaitableEventImpl>) gpu/command_buffer/service/dawn_platform.cc:123:5
#17 0x584afb27f51a in base::internal::Invoker<base::internal::FunctorTraits<void (*&&)(void (*)(void*), void*, gl::ProgressReporter*, scoped_refptr<gpu::webgpu::(anonymous namespace)::AsyncWaitableEventImpl>), void (*&&)(void*), void*&&, base::raw_ptr<gl::ProgressReporter, (partition_alloc::internal::RawPtrTraits)0>&&, scoped_refptr<gpu::webgpu::(anonymous namespace)::AsyncWaitableEventImpl>&&>, base::internal::BindState<false, true, false, void (*)(void (*)(void*), void*, gl::ProgressReporter*, scoped_refptr<gpu::webgpu::(anonymous namespace)::AsyncWaitableEventImpl>), base::internal::UnretainedWrapper<void (void*), base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>, base::internal::UnretainedWrapper<void, base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>, base::internal::UnretainedWrapper<gl::ProgressReporter, base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>, scoped_refptr<gpu::webgpu::(anonymous namespace)::AsyncWaitableEventImpl>>, void ()>::RunOnce(base::internal::BindStateBase*) base/functional/bind_internal.h:673:12
#18 0x584af1cee9c3 in base::TaskAnnotator::RunTaskImpl(base::PendingTask&) base/functional/callback.h:155:12
#19 0x584af1d878c2 in base::internal::TaskTracker::RunTaskImpl(base::internal::Task&, base::TaskTraits const&, base::internal::TaskSource*, base::internal::SequenceToken const&) base/task/common/task_annotator.h:112:5
#20 0x584af1d87b0c in base::internal::TaskTracker::RunSkipOnShutdown(base::internal::Task&, base::TaskTraits const&, base::internal::TaskSource*, base::internal::SequenceToken const&) base/task/thread_pool/task_tracker.cc:675:3
#21 0x584af1d860f9 in base::internal::TaskTracker::RunTask(base::internal::Task, base::internal::TaskSource*, base::TaskTraits const&, base::ThreadType) base/task/thread_pool/task_tracker.cc:705:7
#22 0x584af1d85382 in base::internal::TaskTracker::RunAndPopNextTask(base::internal::RegisteredTaskSource) base/task/thread_pool/task_tracker.cc:393:5
#23 0x584af1dca2b3 in base::internal::WorkerThread::RunWorker() base/task/thread_pool/worker_thread.cc:473:36
#24 0x584af1dc93f4 in base::internal::WorkerThread::RunPooledWorker() base/task/thread_pool/worker_thread.cc:359:3
#25 0x584af1dc8e5b in base::internal::WorkerThread::ThreadMain() base/task/thread_pool/worker_thread.cc:339:7
#26 0x584af1e5263e in base::(anonymous namespace)::ThreadFunc(void*) base/threading/platform_thread_posix.cc:102:13
#27 0x584ad9996f16 in asan_thread_start(void*) asan_interceptors.cpp
0x77a1b624e54c is located 0 bytes after 5196-byte region [0x77a1b624d100,0x77a1b624e54c)
allocated by thread T58 (ThreadPoolForeg) here:
#0 0x584ad99d26ad in operator new[](unsigned long) (/home/slave/chromium-bug-bounty/chromium/src/out/asan/chrome+0x1100c6ad) (BuildId: 8515f221df06fcdf)
#1 0x584adb95217c in dawn::native::BlobCache::LoadInternal(dawn::native::CacheKey const&) third_party/dawn/src/dawn/native/BlobCache.cpp:149:27
#2 0x584adbae09b7 in dawn::native::PipelineCacheBase::Initialize() third_party/dawn/src/dawn/native/PipelineCache.cpp:40:31
#3 0x584adbdff6d8 in dawn::native::vulkan::PipelineCache::Initialize() third_party/dawn/src/dawn/native/vulkan/PipelineCacheVk.cpp:115:36
#4 0x584adbdff58b in dawn::native::vulkan::PipelineCache::Create(dawn::native::vulkan::Device*, dawn::native::CacheKey const&) third_party/dawn/src/dawn/native/vulkan/PipelineCacheVk.cpp:45:12
#5 0x584adbdb62d5 in dawn::native::vulkan::Device::GetOrCreatePipelineCacheImpl(dawn::native::CacheKey const&) third_party/dawn/src/dawn/native/vulkan/DeviceVk.cpp:282:12
#6 0x584adb9eb018 in dawn::native::DeviceBase::GetOrCreatePipelineCache(dawn::native::CacheKey const&) third_party/dawn/src/dawn/native/Device.cpp:1175:12
#7 0x584adbd9fa76 in dawn::native::vulkan::ComputePipeline::InitializeSpecialization(dawn::native::vulkan::CommonPipelineSpecialization const&, bool) third_party/dawn/src/dawn/native/vulkan/ComputePipelineVk.cpp:190:55
#8 0x584adbd9eb67 in dawn::native::vulkan::ComputePipeline::InitializeImpl() third_party/dawn/src/dawn/native/vulkan/ComputePipelineVk.cpp:70:24
#9 0x584adb9ca83c in dawn::native::ComputePipelineBase::InitializeWithShaders() third_party/dawn/src/dawn/native/ComputePipeline.cpp:76:37
#10 0x584adbad6677 in dawn::native::PipelineBase::Initialize(std::__Cr::optional<dawn::native::PerStage<dawn::native::APIRef<dawn::native::ShaderModuleBase>>>) third_party/dawn/src/dawn/native/Pipeline.cpp:445:22
#11 0x584adb9d6abe in dawn::native::CreatePipelineAsyncEvent<dawn::native::ComputePipelineBase, WGPUCreateComputePipelineAsyncCallbackInfo>::InitializeImpl(bool) third_party/dawn/src/dawn/native/CreatePipelineAsyncEvent.cpp:152:33
#12 0x584adb8ec75b in dawn::native::AsyncTask::Run() gen/third_party/libc++/src/include/__functional/function.h:502:12
#13 0x584adb8ed7e6 in dawn::native::AsyncTaskManager::RunTask(void*) third_party/dawn/src/dawn/native/AsyncTask.cpp:169:16
#14 0x584afb27ec33 in gpu::webgpu::(anonymous namespace)::AsyncWorkerTaskPool::RunWorkerTask(void (*)(void*), void*, gl::ProgressReporter*, scoped_refptr<gpu::webgpu::(anonymous namespace)::AsyncWaitableEventImpl>) gpu/command_buffer/service/dawn_platform.cc:123:5
#15 0x584afb27f51a in base::internal::Invoker<base::internal::FunctorTraits<void (*&&)(void (*)(void*), void*, gl::ProgressReporter*, scoped_refptr<gpu::webgpu::(anonymous namespace)::AsyncWaitableEventImpl>), void (*&&)(void*), void*&&, base::raw_ptr<gl::ProgressReporter, (partition_alloc::internal::RawPtrTraits)0>&&, scoped_refptr<gpu::webgpu::(anonymous namespace)::AsyncWaitableEventImpl>&&>, base::internal::BindState<false, true, false, void (*)(void (*)(void*), void*, gl::ProgressReporter*, scoped_refptr<gpu::webgpu::(anonymous namespace)::AsyncWaitableEventImpl>), base::internal::UnretainedWrapper<void (void*), base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>, base::internal::UnretainedWrapper<void, base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>, base::internal::UnretainedWrapper<gl::ProgressReporter, base::unretained_traits::MayNotDangle, (partition_alloc::internal::RawPtrTraits)0>, scoped_refptr<gpu::webgpu::(anonymous namespace)::AsyncWaitableEventImpl>>, void ()>::RunOnce(base::internal::BindStateBase*) base/functional/bind_internal.h:673:12
#16 0x584af1cee9c3 in base::TaskAnnotator::RunTaskImpl(base::PendingTask&) base/functional/callback.h:155:12
#17 0x584af1d878c2 in base::internal::TaskTracker::RunTaskImpl(base::internal::Task&, base::TaskTraits const&, base::internal::TaskSource*, base::internal::SequenceToken const&) base/task/common/task_annotator.h:112:5
#18 0x584af1d87b0c in base::internal::TaskTracker::RunSkipOnShutdown(base::internal::Task&, base::TaskTraits const&, base::internal::TaskSource*, base::internal::SequenceToken const&) base/task/thread_pool/task_tracker.cc:675:3
#19 0x584af1d860f9 in base::internal::TaskTracker::RunTask(base::internal::Task, base::internal::TaskSource*, base::TaskTraits const&, base::ThreadType) base/task/thread_pool/task_tracker.cc:705:7
#20 0x584af1d85382 in base::internal::TaskTracker::RunAndPopNextTask(base::internal::RegisteredTaskSource) base/task/thread_pool/task_tracker.cc:393:5
#21 0x584af1dca2b3 in base::internal::WorkerThread::RunWorker() base/task/thread_pool/worker_thread.cc:473:36
#22 0x584af1dc93f4 in base::internal::WorkerThread::RunPooledWorker() base/task/thread_pool/worker_thread.cc:359:3
#23 0x584af1dc8e5b in base::internal::WorkerThread::ThreadMain() base/task/thread_pool/worker_thread.cc:339:7
#24 0x584af1e5263e in base::(anonymous namespace)::ThreadFunc(void*) base/threading/platform_thread_posix.cc:102:13
#25 0x584ad9996f16 in asan_thread_start(void*) asan_interceptors.cpp
Thread T58 (ThreadPoolForeg) created by T45 (ThreadPoolForeg) here:
#0 0x584ad997cd91 in pthread_create (/home/slave/chromium-bug-bounty/chromium/src/out/asan/chrome+0x10fb6d91) (BuildId: 8515f221df06fcdf)
#1 0x584af1e51c92 in base::(anonymous namespace)::CreateThread(unsigned long, bool, base::PlatformThreadBase::Delegate*, base::PlatformThreadHandle*, base::ThreadType, base::MessagePumpType) base/threading/platform_thread_posix.cc:153:13
#2 0x584af1dc7ba4 in base::internal::WorkerThread::Start(scoped_refptr<base::SingleThreadTaskRunner>, base::WorkerThreadObserver*) base/task/thread_pool/worker_thread.cc:185:3
#3 0x584af1dbf165 in base::internal::ThreadGroup::BaseScopedCommandsExecutor::Flush() base/task/thread_pool/thread_group.cc:65:13
#4 0x584af1dbee20 in base::internal::ThreadGroup::BaseScopedCommandsExecutor::~BaseScopedCommandsExecutor() base/task/thread_pool/thread_group.cc:56:3
#5 0x584af1db6d51 in base::internal::ThreadGroupImpl::WorkerDelegate::GetWork(base::internal::WorkerThread*) base/task/thread_pool/thread_group_impl.cc:71:3
#6 0x584af1dca0d3 in base::internal::WorkerThread::RunWorker() base/task/thread_pool/worker_thread.cc:460:52
#7 0x584af1dc93f4 in base::internal::WorkerThread::RunPooledWorker() base/task/thread_pool/worker_thread.cc:359:3
#8 0x584af1dc8e5b in base::internal::WorkerThread::ThreadMain() base/task/thread_pool/worker_thread.cc:339:7
#9 0x584af1e5263e in base::(anonymous namespace)::ThreadFunc(void*) base/threading/platform_thread_posix.cc:102:13
#10 0x584ad9996f16 in asan_thread_start(void*) asan_interceptors.cpp
Thread T45 (ThreadPoolForeg) created by T35 (ThreadPoolForeg) here:
#0 0x584ad997cd91 in pthread_create (/home/slave/chromium-bug-bounty/chromium/src/out/asan/chrome+0x10fb6d91) (BuildId: 8515f221df06fcdf)
#1 0x584af1e51c92 in base::(anonymous namespace)::CreateThread(unsigned long, bool, base::PlatformThreadBase::Delegate*, base::PlatformThreadHandle*, base::ThreadType, base::MessagePumpType) base/threading/platform_thread_posix.cc:153:13
#2 0x584af1dc7ba4 in base::internal::WorkerThread::Start(scoped_refptr<base::SingleThreadTaskRunner>, base::WorkerThreadObserver*) base/task/thread_pool/worker_thread.cc:185:3
#3 0x584af1dbf165 in base::internal::ThreadGroup::BaseScopedCommandsExecutor::Flush() base/task/thread_pool/thread_group.cc:65:13
#4 0x584af1dbee20 in base::internal::ThreadGroup::BaseScopedCommandsExecutor::~BaseScopedCommandsExecutor() base/task/thread_pool/thread_group.cc:56:3
#5 0x584af1db6d51 in base::internal::ThreadGroupImpl::WorkerDelegate::GetWork(base::internal::WorkerThread*) base/task/thread_pool/thread_group_impl.cc:71:3
#6 0x584af1dca0d3 in base::internal::WorkerThread::RunWorker() base/task/thread_pool/worker_thread.cc:460:52
#7 0x584af1dc93f4 in base::internal::WorkerThread::RunPooledWorker() base/task/thread_pool/worker_thread.cc:359:3
#8 0x584af1dc8e5b in base::internal::WorkerThread::ThreadMain() base/task/thread_pool/worker_thread.cc:339:7
#9 0x584af1e5263e in base::(anonymous namespace)::ThreadFunc(void*) base/threading/platform_thread_posix.cc:102:13
#10 0x584ad9996f16 in asan_thread_start(void*) asan_interceptors.cpp
Thread T35 (ThreadPoolForeg) created by T0 (chrome) here:
#0 0x584ad997cd91 in pthread_create (/home/slave/chromium-bug-bounty/chromium/src/out/asan/chrome+0x10fb6d91) (BuildId: 8515f221df06fcdf)
#1 0x584af1e51c92 in base::(anonymous namespace)::CreateThread(unsigned long, bool, base::PlatformThreadBase::Delegate*, base::PlatformThreadHandle*, base::ThreadType, base::MessagePumpType) base/threading/platform_thread_posix.cc:153:13
#2 0x584af1dc7ba4 in base::internal::WorkerThread::Start(scoped_refptr<base::SingleThreadTaskRunner>, base::WorkerThreadObserver*) base/task/thread_pool/worker_thread.cc:185:3
#3 0x584af1dbf165 in base::internal::ThreadGroup::BaseScopedCommandsExecutor::Flush() base/task/thread_pool/thread_group.cc:65:13
#4 0x584af1dbee20 in base::internal::ThreadGroup::BaseScopedCommandsExecutor::~BaseScopedCommandsExecutor() base/task/thread_pool/thread_group.cc:56:3
#5 0x584af1db47f4 in base::internal::ThreadGroupImpl::Start(unsigned long, unsigned long, base::TimeDelta, scoped_refptr<base::SingleThreadTaskRunner>, base::WorkerThreadObserver*, base::internal::ThreadGroup::WorkerEnvironment, bool, std::__Cr::optional<base::TimeDelta>) base/task/thread_pool/thread_group_impl.cc:71:3
#6 0x584af1d8d5e7 in base::internal::ThreadPoolImpl::Start(base::ThreadPoolInstance::InitParams const&, base::WorkerThreadObserver*) base/task/thread_pool/thread_pool_impl.cc:241:35
#7 0x584af1dc693c in base::ThreadPoolInstance::StartWithDefaultParams() base/task/thread_pool/thread_pool_instance.cc:95:3
#8 0x584afe0e6642 in content::ChildProcess::ChildProcess(base::ThreadType, std::__Cr::unique_ptr<base::ThreadPoolInstance::InitParams, std::__Cr::default_delete<base::ThreadPoolInstance::InitParams>>, bool) content/child/child_process.cc:114:20
#9 0x584afd6299fd in content::GpuMain(content::MainFunctionParams) content/gpu/gpu_main.cc:426:16
#10 0x584aedd8600f in content::RunZygote(content::ContentMainDelegate*) content/app/content_main_runner_impl.cc:665:14
#11 0x584aedd87347 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:772:12
#12 0x584aedd8a2b8 in content::ContentMainRunnerImpl::Run() content/app/content_main_runner_impl.cc:1164:10
#13 0x584aedd83a11 in content::RunContentProcess(content::ContentMainParams, content::ContentMainRunner*) content/app/content_main.cc:356:36
#14 0x584aedd8400c in content::ContentMain(content::ContentMainParams) content/app/content_main.cc:369:10
#15 0x584ad99d42a9 in ChromeMain chrome/app/chrome_main.cc:194:12
#16 0x7981b7a2a1c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#17 0x7981b7a2a28a in __libc_start_main csu/../csu/libc-start.c:360:3
#18 0x584ad98f9029 in _start (/home/slave/chromium-bug-bounty/chromium/src/out/asan/chrome+0x10f33029) (BuildId: 8515f221df06fcdf)
SUMMARY: AddressSanitizer: heap-buffer-overflow (/home/slave/chromium-bug-bounty/chromium/src/out/asan/chrome+0x10fd1907) (BuildId: 8515f221df06fcdf) in __asan_memmove
Shadow bytes around the buggy address:
0x77a1b624e280: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x77a1b624e300: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x77a1b624e380: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x77a1b624e400: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x77a1b624e480: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x77a1b624e500: 00 00 00 00 00 00 00 00 00[04]fa fa fa fa fa fa
0x77a1b624e580: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x77a1b624e600: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x77a1b624e680: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x77a1b624e700: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x77a1b624e780: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
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
==250513==ADDITIONAL INFO
==250513==Note: Please include this section with the ASan report.
Task trace:
#0 0x584afb27e24d in gpu::webgpu::(anonymous namespace)::AsyncWorkerTaskPool::PostWorkerTask(void (*)(void*), void*) gpu/command_buffer/service/dawn_platform.cc:99:9
#1 0x584ae4c3cdc2 in gpu::Scheduler::RunNextTask() gpu/command_buffer/service/scheduler.cc:649:27
#2 0x584ae4c3cdc2 in gpu::Scheduler::RunNextTask() gpu/command_buffer/service/scheduler.cc:649:27
#3 0x584ae4c3cdc2 in gpu::Scheduler::RunNextTask() gpu/command_buffer/service/scheduler.cc:649:27
Command line: `/proc/self/exe --type=gpu-process --no-sandbox --ozone-platform=x11 --crashpad-handler-pid=250455 --enable-crash-reporter=, --change-stack-guard-on-fork=enable --gpu-preferences=UAAAAAAAAAAgAQAEAAAAAAAAAAAAAMAAAgAAAAIAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAYAAAAAAAAABgAAAAAAAAAAQAAAAAAAAAIAAAAAAAAAAgAAAAAAAAA --shared-files --metrics-shmem-handle=4,i,7209806234512302793,10646766739262038649,262144 --field-trial-handle=3,i,930760376122516794,15094128488077092875,262144 --enable-features=Vulkan --variations-seed-version --pseudonymization-salt-handle=7,i,17392357467876290775,17762540892605258158,4 --trace-process-track-uuid=3190708988185955192`
==250513==END OF ADDITIONAL INFO
==250513==ABORTING
[251437:271836:0418/153449.465174:ERROR:gpu/ipc/client/command_buffer_proxy_impl.cc:285] ContextResult::kTransientFailure: Failed to send GpuControl.CreateCommandBuffer.
[251437:271831:0418/153449.465177:ERROR:gpu/ipc/client/command_buffer_proxy_impl.cc:285] ContextResult::kTransientFailure: Failed to send GpuControl.CreateCommandBuffer.
[251437:271835:0418/153449.465184:ERROR:gpu/ipc/client/command_buffer_proxy_impl.cc:285] ContextResult::kTransientFailure: Failed to send GpuControl.CreateCommandBuffer.
[251437:271830:0418/153449.465184:ERROR:gpu/ipc/client/command_buffer_proxy_impl.cc:285] ContextResult::kTransientFailure: Failed to send GpuControl.CreateCommandBuffer.
...
Type of crash: GPU-process AddressSanitizer heap-buffer-overflow
CREDIT INFORMATION
Reporter credit: Hyeonjun Ahn (@_deayzl)