CVE-2026-8582
Overview
Files Changed
src/dawn/native/BlitTextureToBuffer.cpp
Patch
From 8d862f1501d19ebe7e1e030fc71158a015d0db64 Mon Sep 17 00:00:00 2001 From: Shrek Shao <[email protected]> Date: Tue, 31 Mar 2026 01:18:05 -0700 Subject: [PATCH] BlitTextureToBuffer.cpp: slightly defer dst.buffer->SetInitialized call This CL slightly makes the code nicer and more correct. Moves `dst.buffer->SetInitialized` to - After several `device->Create*` that may throw error so the internal mIsDataInitialized is less likely leaking. - Before the commandEncoder encoding so it can still skip the extra clearing when not needed. Bug: 497594413 Change-Id: Ic7e5e14d5956500edbda017cf9eba910b15d777c Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/300316 Commit-Queue: Corentin Wallez <[email protected]> Auto-Submit: Shrek Shao <[email protected]> Reviewed-by: Corentin Wallez <[email protected]> --- diff --git a/src/dawn/native/BlitTextureToBuffer.cpp b/src/dawn/native/BlitTextureToBuffer.cpp index b7c606a..2e84864 100644 --- a/src/dawn/native/BlitTextureToBuffer.cpp +++ b/src/dawn/native/BlitTextureToBuffer.cpp @@ -1163,8 +1163,6 @@ const bool fullSizeCopy = IsFullBufferOverwrittenInTextureToBufferCopy( src, dst, blockInfo.ToTexel(copyExtent).ToExtent3D()); - // Skip clearing the buffer if this is full size copy. - dst.buffer->SetInitialized(fullSizeCopy || dst.buffer->IsInitialized()); Ref<BufferBase> destinationBuffer = dst.buffer.Get(); const uint64_t numBytesToCopy = @@ -1353,6 +1351,9 @@ UsageValidationMode::Internal)); } + // Skip clearing the buffer if this is full size copy. + dst.buffer->SetInitialized(fullSizeCopy || dst.buffer->IsInitialized()); + Ref<ComputePassEncoder> pass = commandEncoder->BeginComputePass(); pass->APISetPipeline(pipeline.Get()); pass->APISetBindGroup(0, bindGroup0.Get());
Original Bug Report
GPU memory information leak via premature SetInitialized in BlitTextureToBuffer
Project Fortify, an experimental security project, has identified the following potential security issue.
Overview: Dawn’s BlitTextureToBuffer prematurely marks destination buffers as initialized before performing fallible internal operations like memory allocation. If an allocation fails (e.g., OOM) and the CommandEncoder is abandoned, the error is silently discarded while the buffer remains marked as initialized, bypassing LazyClearResourceOnFirstUse and allowing an attacker to map uninitialized GPU memory.
Affected files:
third_party/dawn/src/dawn/native/BlitTextureToBuffer.cppthird_party/dawn/src/dawn/native/Buffer.cppthird_party/dawn/src/dawn/native/EncodingContext.cpp
Estimated timestamp from git blame: 2024-07-02
Description
In third_party/dawn/src/dawn/native/BlitTextureToBuffer.cpp, the BlitTextureToBuffer function is used as a workaround for certain texture-to-buffer copies on backends like OpenGL, GLES, and D3D11. This function prematurely updates the initialized state of the destination buffer before performing fallible internal operations.
At line 1167 in BlitTextureToBuffer.cpp:
dst.buffer->SetInitialized(fullSizeCopy || dst.buffer->IsInitialized());
If fullSizeCopy is true (meaning the copy operation is expected to overwrite the entire buffer), the destination buffer is immediately marked as initialized.
Following this, several internal operations occur that can fail, most notably device->CreateBuffer (e.g., at lines 1199 and 1253) which may fail with an Out Of Memory (OOM) error if GPU memory is exhausted. When such an error occurs, the DAWN_TRY or DAWN_TRY_ASSIGN macros cause the function to return early. In the context of a CommandEncoder, this error is deferred into the EncodingContext.
If the attacker then releases the CommandEncoder without calling finish(), the deferred error is silently discarded during EncodingContext::Destroy(). No device-lost event occurs, and the destination buffer remains in the incorrectly marked ‘initialized’ state.
Because the recording of the BlitTextureToBuffer commands was aborted, the destination buffer’s contents are never actually updated or cleared. However, since the buffer is marked as initialized, subsequent operations (such as MapAsync or using the buffer as a source in another copy) will evaluate NeedsInitialization() as false. This skips the LazyClearResourceOnFirstUse step (verified in Buffer.cpp), which is intended to zero-initialize buffers before their first use. This allows an attacker to read uninitialized GPU memory, potentially containing sensitive data from other WebGPU contexts or previous allocations in the same GPU process.
Note: This analysis relies on source code review. The steps below outline a potential exploit strategy but have not been verified with a live Proof of Concept.
Potential Reproduction Steps
- Use a WebGPU-enabled browser on a platform using the OpenGLES or D3D11 backend.
- Create a
GPUBuffer(B) withCOPY_DST | MAP_READusage and a size matching a planned copy. - Create a
GPUTexture(T) with a format that triggers a blit workaround (e.g.,depth16unormon GLES). - Induce GPU memory pressure to ensure internal allocations fail (e.g., by allocating many large buffers until near the driver limit).
- Use a
GPUCommandEncoderto callcopyTextureToBuffer({texture: T}, {buffer: B}, ...)such that it covers the full buffer (fullSizeCopybecomes true). - Internal
CreateBuffercalls withinBlitTextureToBufferfail due to OOM. The error is deferred in the encoder’sEncodingContext. - Destroy/Release the
GPUCommandEncoderwithout callingfinish(). The deferred error is silently dropped duringEncodingContext::Destroy(). - Release the pressure buffers from step 4.
- Call
B.mapAsync(GPUMapMode.READ)and read the buffer’s contents viagetMappedRange(). The contents will be uninitialized GPU memory instead of zeros, asLazyClearResourceOnFirstUseis bypassed.
Suggested Fix
Ensure that the destination buffer is only marked as initialized after all fallible operations in BlitTextureToBuffer have succeeded, or defer the SetInitialized(true) call until the command is actually submitted or executed. Alternatively, track whether the initialization was performed by a specific command and revert the state if the command fails to encode or is discarded.
Evaluated with Chrome root at commit: a9cbf6e8b275fe4147435aa905f3b7f5a656f5f0
Results from 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.