CVE-2026-13825
Overview
Background
- `Blob`
- Dawn’s owning byte-buffer type (
src/dawn/native/Blob.cpp) that wraps amDataspan plus amDeleter, and can be sliced viaBlob::Create(Blob&&, offset). - `VkPipelineCache`
- a Vulkan object that stores compiled pipeline state, whose serialized bytes are retrieved with
vkGetPipelineCacheData()and persisted by Dawn’s blob cache for reuse across runs. - `vkGetPipelineCacheData()`
- the Vulkan entry point that copies pipeline-cache contents into a caller-supplied buffer and reports how many bytes were actually written.
- `VulkanIncompletePipelineCacheWorkaround`
- a Dawn workaround flag for drivers that return corrupted or incomplete pipeline-cache data, signaling that the retrieved bytes must not be trusted.
Root Cause Analysis
When PipelineCacheVk serialized its VkPipelineCache, it first queried the required size, allocated a Blob of that size with Blob::Create(size), and then called vkGetPipelineCacheData() to fill it. The Vulkan spec permits the driver to write fewer bytes than the previously reported size, but the code assumed the allocated blob was fully populated and stored the entire buffer — including the trailing bytes the driver never wrote — into the persistent blob cache. Because the allocated blob’s tail was never initialized, this leaked uninitialized heap memory into the cache and could feed it back on later runs.
The fix compares the actual bufferSize returned against blob->Size() and, when it is smaller, shrinks the blob to exactly the written extent via the new Blob::Create(std::move(*blob), 0, bufferSize) overload; it additionally clears the blob (*blob = {}) when VulkanIncompletePipelineCacheWorkaround fires so suspected-corrupt data is never persisted.
vkGetPipelineCacheData() actually reports.Attack Path
- Trigger cache serialization
Cause Dawn to build and serialize a Vulkan pipeline cache, invoking
PipelineCacheVk’s store path that allocates aBlobsized to the pre-queried cache size. - Driver underfills the buffer
On a driver that writes fewer bytes than reported,
vkGetPipelineCacheData()returns abufferSizesmaller than the allocatedblob->Size(), leaving the blob’s tail uninitialized. - Uninitialized data is persisted The unpatched code stores the full-size blob, including the uninitialized tail, into the persistent blob cache.
- Read back leaked memory An attacker with access to the on-disk cache (or code that later deserializes and inspects it) recovers the stale heap bytes, potentially disclosing sensitive process memory.
Impact Assessment
vkGetPipelineCacheData() writes less than the queried size (or that triggers VulkanIncompletePipelineCacheWorkaround) and the ability to observe the serialized cache contents. The severity is limited to information leakage rather than direct memory corruption.Files Changed
src/dawn/native/Blob.cppsrc/dawn/native/Blob.hsrc/dawn/native/vulkan/PipelineCacheVk.cpp
Audit Directions
- Report-then-fill APIsAudit any code that pre-queries a size, allocates a buffer, then calls a fill API assuming full population; verify the actually-written count is used for serialization or hashing.
- Serialized/cached buffersReview paths that persist allocated buffers to disk or the blob cache to confirm no uninitialized or padding bytes are ever stored.
- Driver workaround flagsTrace every workaround like
VulkanIncompletePipelineCacheWorkaroundto ensure that data flagged as suspect is discarded rather than silently propagated.
Patch
From e7eb019e0d8237395c80b6df7790085d09591105 Mon Sep 17 00:00:00 2001 From: Kyle Charbonneau <[email protected]> Date: Fri, 15 May 2026 07:40:37 -0700 Subject: [PATCH] Trim data returned for VkPipelineCache vkCreateGraphicsPipeline() can write less data than the allocated blob stores. If that happens shrink the blob to match the size written. Also if VulkanIncompletePipelineCacheWorkaround is triggered then ensure it's not stored into the cache. The data is suspected to be corrupted if the workaround triggers. Bug: 513209610 Change-Id: I4490dcecdd2a5a18d3118557d8fe233f37ac2ac8 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/308856 Commit-Queue: Kyle Charbonneau <[email protected]> Reviewed-by: Loko Kung <[email protected]> --- diff --git a/src/dawn/native/Blob.cpp b/src/dawn/native/Blob.cpp index 1c6072d..784a0f4 100644 --- a/src/dawn/native/Blob.cpp +++ b/src/dawn/native/Blob.cpp @@ -53,8 +53,8 @@ } // static -Blob Blob::Create(Blob&& original, size_t offset) { - Blob result(original.mData.subspan(offset), std::move(original.mDeleter)); +Blob Blob::Create(Blob&& original, size_t offset, size_t extent) { + Blob result(original.mData.subspan(offset, extent), std::move(original.mDeleter)); original.mData = {}; original.mDeleter = nullptr; return result; diff --git a/src/dawn/native/Blob.h b/src/dawn/native/Blob.h index 195c5bf1..d398a56 100644 --- a/src/dawn/native/Blob.h +++ b/src/dawn/native/Blob.h @@ -47,8 +47,9 @@ // Creates a blob of the given size. static Blob Create(size_t size); - // Takes ownership of the original blob, creating a new one that offsets the data by |offset|. - static Blob Create(Blob&& original, size_t offset); + // Takes ownership of the original blob, creating a new one with subspan of the original using + // `offset` and optionally `extent`. + static Blob Create(Blob&& original, size_t offset, size_t extent = std::dynamic_extent); template <typename T> static Blob Create(std::vector<T> vec) diff --git a/src/dawn/native/vulkan/PipelineCacheVk.cpp b/src/dawn/native/vulkan/PipelineCacheVk.cpp index 26ef309..bfa7fcd 100644 --- a/src/dawn/native/vulkan/PipelineCacheVk.cpp +++ b/src/dawn/native/vulkan/PipelineCacheVk.cpp @@ -28,6 +28,7 @@ #include "dawn/native/vulkan/PipelineCacheVk.h" #include <memory> +#include <utility> #include "dawn/native/Device.h" #include "dawn/native/Error.h" @@ -102,10 +103,18 @@ // store it to the blob cache and don't call vkGetPipelineCacheData() since it will return // corrupted data in future calls. mSkipSerialize = true; + *blob = {}; return {}; } DAWN_TRY(CheckVkSuccess(result, "GetPipelineCacheData")); + + if (bufferSize < blob->Size()) { + // vkGetPipelineCacheData() returned less data than expected. Shrink the blob so + // uninitialized data isn't stored in cache. + *blob = Blob::Create(std::move(*blob), 0, bufferSize); + } + mStoredDataSize = bufferSize; return {};
Original Bug Report
Potential uninitialized memory persistence and corruption in Dawn Vulkan pipeline cache
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. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: A logic error in Dawn’s Vulkan backend allows uninitialized GPU process heap memory to be persisted to the on-disk shader cache when a specific driver workaround is triggered. This occurs because the code fails to clear a previously allocated buffer after an incomplete serialization attempt. The persisted data is later reloaded and passed to the Vulkan driver, potentially leading to memory corruption.
Affected files:
third_party/dawn/src/dawn/native/vulkan/PipelineCacheVk.cppthird_party/dawn/src/dawn/native/PipelineCache.cppthird_party/dawn/src/dawn/native/Blob.cpp
Estimated timestamp from git blame: Unknown (Google3 checkout)
Description
A potential logic error exists in PipelineCache::SerializeToBlobImpl within Dawn’s Vulkan backend that can cause uninitialized heap memory to be persisted to the persistent blob cache. This occurs when the VulkanIncompletePipelineCacheWorkaround is triggered on specific hardware (e.g., Pixel 10, Samsung Xclipse).
Root Cause Analysis
In third_party/dawn/src/dawn/native/vulkan/PipelineCacheVk.cpp, the function SerializeToBlobImpl handles the serialization of the Vulkan pipeline cache:
- At line 95, a blob is allocated to hold the cache data:
*blob = Blob::Create(bufferSize);. Blob::Createinthird_party/dawn/src/dawn/native/Blob.cpp(line 46) allocates memory vianew uint8_t[size], which leaves the buffer uninitialized, containing residual data from the GPU process heap.vkGetPipelineCacheDatais called to fill this buffer (line 96).- If the call returns
VK_INCOMPLETEand the workaround is enabled (line 99), the code setsmSkipSerialize = trueand returns success (return {};) at line 105.
However, the function fails to clear or reset the *blob out-parameter before returning. The caller, PipelineCacheBase::Flush() in third_party/dawn/src/dawn/native/PipelineCache.cpp, subsequently checks if (blob.Size() > 0) at line 62. Since the blob was assigned an uninitialized buffer of bufferSize, this condition is true, and the uninitialized memory is stored to the persistent cache via mCache->Store(mKey, blob); (line 65).
Potential Impact
- Information Leak: Sensitive residue from the GPU process heap is written to the persistent shader cache on disk.
- Memory Corruption: On subsequent sessions, the persisted uninitialized blob is reloaded and passed as
pInitialDatatovkCreatePipelineCache(line 130 ofPipelineCacheVk.cpp). If the driver’s binary parser is not robust against malformed or uninitialized input, this could lead to memory corruption within the GPU process. - Sandbox Escape (Android): On Android, the GPU process is typically unsandboxed by default. Memory corruption in this process could allow an attacker to achieve arbitrary code execution with the user’s full privileges.
Potential Reproduction Steps (Suggested)
Note: These steps are theoretical as they rely on specific hardware behavior and have not been validated by a running exploit.
- Use an affected Android device (e.g., Pixel 10 or Samsung Xclipse) with WebGPU enabled.
- From a web page, trigger the compilation of a new shader (e.g., via
createComputePipeline). - The Vulkan driver returns
VK_INCOMPLETEduring cache serialization. - Dawn’s workaround triggers but fails to clear the blob out-parameter.
- The uninitialized heap memory is written to the on-disk cache.
- Restart the browser and re-trigger the same pipeline creation. The corrupted cache is loaded and passed to the driver’s
vkCreatePipelineCache, potentially causing corruption.
Suggested Fix
Ensure the *blob out-parameter is cleared before returning in the VK_INCOMPLETE workaround path in third_party/dawn/src/dawn/native/vulkan/PipelineCacheVk.cpp:
if (result == VK_INCOMPLETE && mInvalidResultWorkaround) {
mSkipSerialize = true;
*blob = Blob(); // Clear the out-parameter to prevent storage
return {};
}
Evaluated with Chrome root at commit: b3153093eb3c78c3e88ccf562bcbc20437a04b0e
Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:
- If you are familiar with the severity guidelines, you may adjust the severity.
- If this is a false positive, and there’s no work to be done, please close as WAI.
- If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.
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.