Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactObject lifecycle issue in Dawn
DescriptionObject lifecycle issue in Dawn
ComponentDawn
Bug ClassLogic Error
Tracker497594413
Fix commit8d862f1501d1 (dawn) +3/-2
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-12

Files Changed

  • src/dawn/native/BlitTextureToBuffer.cpp
From 8d862f1501d19ebe7e1e030fc71158a015d0db64 Mon Sep 17 00:00:00 2001
From: Shrek Shao <shrekshao@google.com>
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 <cwallez@chromium.org>
Auto-Submit: Shrek Shao <shrekshao@google.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
---

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());
Loading diff…

Original Bug Report

reported by vm...@google.com

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.cpp
  • third_party/dawn/src/dawn/native/Buffer.cpp
  • third_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

  1. Use a WebGPU-enabled browser on a platform using the OpenGLES or D3D11 backend.
  2. Create a GPUBuffer (B) with COPY_DST | MAP_READ usage and a size matching a planned copy.
  3. Create a GPUTexture (T) with a format that triggers a blit workaround (e.g., depth16unorm on GLES).
  4. Induce GPU memory pressure to ensure internal allocations fail (e.g., by allocating many large buffers until near the driver limit).
  5. Use a GPUCommandEncoder to call copyTextureToBuffer({texture: T}, {buffer: B}, ...) such that it covers the full buffer (fullSizeCopy becomes true).
  6. Internal CreateBuffer calls within BlitTextureToBuffer fail due to OOM. The error is deferred in the encoder’s EncodingContext.
  7. Destroy/Release the GPUCommandEncoder without calling finish(). The deferred error is silently dropped during EncodingContext::Destroy().
  8. Release the pressure buffers from step 4.
  9. Call B.mapAsync(GPUMapMode.READ) and read the buffer’s contents via getMappedRange(). The contents will be uninitialized GPU memory instead of zeros, as LazyClearResourceOnFirstUse is 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.

View on issue tracker