Medium chrome Uninitialized Memory 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUninitialized Use in Dawn
DescriptionUninitialized Use in Dawn
ComponentDawn
Bug ClassUninitialized Memory
Tracker513631768
Fix commit6ea97833a636 (dawn) +92/-5
CISA KEVNot listed
CreditedChrovus
Disclosed2026-06-30

Changed Functions

FunctionChangeNotes
BlitTextureToBufferTest
src/dawn/tests/end2end/CopyTests.cpp
modified
TEST_P
src/dawn/tests/end2end/CopyTests.cpp
modified

Files Changed

  • src/dawn/native/BlitTextureToBuffer.cpp
  • src/dawn/tests/end2end/BufferZeroInitTests.cpp
  • src/dawn/tests/end2end/CopyTests.cpp
  • src/dawn/tests/end2end/TextureZeroInitTests.cpp
From 6ea97833a636247bc137ee6d24efd0ac7a68111d Mon Sep 17 00:00:00 2001
From: Shrek Shao <[email protected]>
Date: Wed, 20 May 2026 11:06:10 -0700
Subject: [PATCH] Remove the optimization of a clearing buffer in BlitTextureToBuffer

This optimization is temporarily removed because we cannot mark
the buffer as initialized until the command buffer is submitted.

FIXED: 513631768
Change-Id: Ie6f539e07139c5eefef9ddf87231af346d7a9e28
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/309588
Auto-Submit: Shrek Shao <[email protected]>
Reviewed-by: Loko Kung <[email protected]>
Commit-Queue: Shrek Shao <[email protected]>
---

diff --git a/src/dawn/native/BlitTextureToBuffer.cpp b/src/dawn/native/BlitTextureToBuffer.cpp
index 22fa7f2..b663bfc 100644
--- a/src/dawn/native/BlitTextureToBuffer.cpp
+++ b/src/dawn/native/BlitTextureToBuffer.cpp
@@ -1354,8 +1354,11 @@
                                                          UsageValidationMode::Internal));
     }
 
-    // Skip clearing the buffer if this is full size copy.
-    dst.buffer->SetInitialized(fullSizeCopy || dst.buffer->IsInitialized());
+    // TODO(b/513631768): Skip clearing the buffer if this is full size copy.
+    // dst.buffer->SetInitialized(fullSizeCopy || dst.buffer->IsInitialized());
+    //
+    // This optimization is temporarily removed because we cannot mark the buffer as initialized
+    // until the command buffer is submitted.
 
     Ref<ComputePassEncoder> pass = commandEncoder->BeginComputePass();
     pass->APISetPipeline(pipeline.Get());
diff --git a/src/dawn/tests/end2end/BufferZeroInitTests.cpp b/src/dawn/tests/end2end/BufferZeroInitTests.cpp
index 6d86a10..625446a 100644
--- a/src/dawn/tests/end2end/BufferZeroInitTests.cpp
+++ b/src/dawn/tests/end2end/BufferZeroInitTests.cpp
@@ -145,7 +145,14 @@
         wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
         encoder.CopyTextureToBuffer(&texelCopyTextureInfo, &texelCopyBufferInfo, &spec.textureSize);
         wgpu::CommandBuffer commandBuffer = encoder.Finish();
-        EXPECT_LAZY_CLEAR(spec.lazyClearCount, queue.Submit(1, &commandBuffer));
+
+        // TODO(b/513631768): SetInitialized is now skipped for use_blit_for_t2b path.
+        uint32_t expectedLazyClearCount = spec.lazyClearCount;
+        if (expectedLazyClearCount == 0u && (HasToggleEnabled("use_blit_for_t2b"))) {
+            expectedLazyClearCount = 1u;
+        }
+
+        EXPECT_LAZY_CLEAR(expectedLazyClearCount, queue.Submit(1, &commandBuffer));
 
         const uint64_t expectedValueCount = bufferSize / sizeof(float);
         std::vector<float> expectedValues(expectedValueCount, 0.f);
@@ -1153,6 +1160,10 @@
     // TODO(crbug.com/473593119): [Capture] size not multiple of 4.
     DAWN_SUPPRESS_TEST_IF(IsCaptureReplayCheckingEnabled());
 
+    // TODO(crbug.com/513631768): Skip D3D11 as they may use intermediate buffer for blit
+    // path and fail for EXPECT_LAZY_CLEAR.
+    DAWN_SUPPRESS_TEST_IF(IsD3D11());
+
     constexpr wgpu::TextureFormat kColorAttachmentFormat = wgpu::TextureFormat::RGBA8Unorm;
     // A small sub-4-byte format means a single vertex can fit entirely within the padded buffer,
     // touching some of the padding. Test a small format, as well as larger formats.
@@ -1222,7 +1233,13 @@
                     encoder.CopyTextureToBuffer(&zeroTextureSrc, &dst, &extent);
 
                     wgpu::CommandBuffer commandBuffer = encoder.Finish();
-                    EXPECT_LAZY_CLEAR(0u, queue.Submit(1, &commandBuffer));
+
+                    // TODO(b/513631768): SetInitialized is now skipped for use_blit_for_t2b path.
+                    uint32_t expectedLazyClearCount = 0u;
+                    if (HasToggleEnabled("use_blit_for_t2b")) {
+                        expectedLazyClearCount = 1u;
+                    }
+                    EXPECT_LAZY_CLEAR(expectedLazyClearCount, queue.Submit(1, &commandBuffer));
                 }
 
                 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
diff --git a/src/dawn/tests/end2end/CopyTests.cpp b/src/dawn/tests/end2end/CopyTests.cpp
index 64da8d2..baad726 100644
--- a/src/dawn/tests/end2end/CopyTests.cpp
+++ b/src/dawn/tests/end2end/CopyTests.cpp
@@ -4302,5 +4302,65 @@
                           // clang-format on
                       }));
 
+class BlitTextureToBufferTest : public DawnTest {};
+
+// Test that encoding a CopyTextureToBuffer but not submitting it doesn't mark the buffer as
+// initialized.
+TEST_P(BlitTextureToBufferTest, NoSubmitDoesNotMarkInitialized) {
+    DAWN_TEST_UNSUPPORTED_IF(UsesWire());
+
+    // Create a buffer that we will use as the destination of a CopyTextureToBuffer.
+    wgpu::BufferDescriptor bufferDesc;
+    bufferDesc.size = 256;
+    bufferDesc.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
+    wgpu::Buffer buffer = device.CreateBuffer(&bufferDesc);
+
+    // Create a source texture.
+    wgpu::TextureDescriptor textureDesc;
+    textureDesc.size = {64, 1, 1};
+    textureDesc.format = wgpu::TextureFormat::R32Float;
+    textureDesc.usage = wgpu::TextureUsage::CopySrc;
+    wgpu::Texture texture = device.CreateTexture(&textureDesc);
+
+    wgpu::TexelCopyTextureInfo src = utils::CreateTexelCopyTextureInfo(texture, 0, {0, 0, 0});
+    wgpu::TexelCopyBufferInfo dst = utils::CreateTexelCopyBufferInfo(buffer, 0, 256, 1);
+    wgpu::Extent3D copySize = {64, 1, 1};
+
+    // Encode the CopyTextureToBuffer.
+    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+    encoder.CopyTextureToBuffer(&src, &dst, &copySize);
+    // Finish the encoder, but do NOT submit the command buffer.
+    encoder.Finish();
+
+    // Now, if we use the buffer in a way that requires initialization (e.g., as a source of a
+    // copy), it should be lazy-cleared because the previous CopyTextureToBuffer was never
+    // submitted.
+    wgpu::BufferDescriptor dstBufferDesc;
+    dstBufferDesc.size = 256;
+    dstBufferDesc.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc;
+    wgpu::Buffer dstBuffer = device.CreateBuffer(&dstBufferDesc);
+
+    wgpu::CommandEncoder encoder2 = device.CreateCommandEncoder();
+    encoder2.CopyBufferToBuffer(buffer, 0, dstBuffer, 0, 256);
+    wgpu::CommandBuffer cb = encoder2.Finish();
+
+    size_t lazyClearsBefore = native::GetLazyClearCountForTesting(device.Get());
+    queue.Submit(1, &cb);
+    size_t lazyClearsAfter = native::GetLazyClearCountForTesting(device.Get());
+
+    // If the buffer was incorrectly marked as initialized during encoding of the first command,
+    // lazyClearsAfter - lazyClearsBefore will be 0.
+    // Otherwise, it should be 1.
+    EXPECT_EQ(lazyClearsAfter - lazyClearsBefore, 1u);
+}
+
+DAWN_INSTANTIATE_TEST(BlitTextureToBufferTest,
+                      D3D11Backend({"use_blit_for_t2b"}),
+                      D3D12Backend({"use_blit_for_t2b"}),
+                      MetalBackend({"use_blit_for_t2b"}),
+                      OpenGLBackend({"use_blit_for_t2b"}),
+                      OpenGLESBackend({"use_blit_for_t2b"}),
+                      VulkanBackend({"use_blit_for_t2b"}));
+
 }  // anonymous namespace
 }  // namespace dawn
diff --git a/src/dawn/tests/end2end/TextureZeroInitTests.cpp b/src/dawn/tests/end2end/TextureZeroInitTests.cpp
index 7f6f0d7..97a5481 100644
--- a/src/dawn/tests/end2end/TextureZeroInitTests.cpp
+++ b/src/dawn/tests/end2end/TextureZeroInitTests.cpp
@@ -311,7 +311,14 @@
     wgpu::CommandBuffer commandBuffer = encoder.Finish();
 
     // Expect texture to be lazy initialized.
-    EXPECT_LAZY_CLEAR(1u, queue.Submit(1, &commandBuffer));
+    // TODO(b/513631768): SetInitialized is now skipped for use_blit_for_t2b path.
+    // If blit is used for T2B, the destination buffer is NOT marked as initialized during encoding,
+    // so it will also be lazy cleared during the first usage (which is the blit itself).
+    uint32_t expectedLazyClearCount = 1u;
+    if (HasToggleEnabled("use_blit_for_t2b")) {
+        expectedLazyClearCount++;
+    }
+    EXPECT_LAZY_CLEAR(expectedLazyClearCount, queue.Submit(1, &commandBuffer));
 
     // Expect texture subresource initialized to be true
     EXPECT_TRUE(native::IsTextureSubresourceInitialized(texture.Get(), 0, 1, 0, kArrayLayers));
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/src/dawn/tests/end2end/BufferZeroInitTests.cpp b/src/dawn/tests/end2end/BufferZeroInitTests.cpp
index 6d86a10..625446a 100644
--- a/src/dawn/tests/end2end/BufferZeroInitTests.cpp
+++ b/src/dawn/tests/end2end/BufferZeroInitTests.cpp
@@ -145,7 +145,14 @@
         wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
         encoder.CopyTextureToBuffer(&texelCopyTextureInfo, &texelCopyBufferInfo, &spec.textureSize);
         wgpu::CommandBuffer commandBuffer = encoder.Finish();
-        EXPECT_LAZY_CLEAR(spec.lazyClearCount, queue.Submit(1, &commandBuffer));
+
+        // TODO(b/513631768): SetInitialized is now skipped for use_blit_for_t2b path.
+        uint32_t expectedLazyClearCount = spec.lazyClearCount;
+        if (expectedLazyClearCount == 0u && (HasToggleEnabled("use_blit_for_t2b"))) {
+            expectedLazyClearCount = 1u;
+        }
+
+        EXPECT_LAZY_CLEAR(expectedLazyClearCount, queue.Submit(1, &commandBuffer));
 
         const uint64_t expectedValueCount = bufferSize / sizeof(float);
         std::vector<float> expectedValues(expectedValueCount, 0.f);
@@ -1153,6 +1160,10 @@
     // TODO(crbug.com/473593119): [Capture] size not multiple of 4.
     DAWN_SUPPRESS_TEST_IF(IsCaptureReplayCheckingEnabled());
 
+    // TODO(crbug.com/513631768): Skip D3D11 as they may use intermediate buffer for blit
+    // path and fail for EXPECT_LAZY_CLEAR.
+    DAWN_SUPPRESS_TEST_IF(IsD3D11());
+
     constexpr wgpu::TextureFormat kColorAttachmentFormat = wgpu::TextureFormat::RGBA8Unorm;
     // A small sub-4-byte format means a single vertex can fit entirely within the padded buffer,
     // touching some of the padding. Test a small format, as well as larger formats.
@@ -1222,7 +1233,13 @@
                     encoder.CopyTextureToBuffer(&zeroTextureSrc, &dst, &extent);
 
                     wgpu::CommandBuffer commandBuffer = encoder.Finish();
-                    EXPECT_LAZY_CLEAR(0u, queue.Submit(1, &commandBuffer));
+
+                    // TODO(b/513631768): SetInitialized is now skipped for use_blit_for_t2b path.
+                    uint32_t expectedLazyClearCount = 0u;
+                    if (HasToggleEnabled("use_blit_for_t2b")) {
+                        expectedLazyClearCount = 1u;
+                    }
+                    EXPECT_LAZY_CLEAR(expectedLazyClearCount, queue.Submit(1, &commandBuffer));
                 }
 
                 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
diff --git a/src/dawn/tests/end2end/CopyTests.cpp b/src/dawn/tests/end2end/CopyTests.cpp
index 64da8d2..baad726 100644
--- a/src/dawn/tests/end2end/CopyTests.cpp
+++ b/src/dawn/tests/end2end/CopyTests.cpp
@@ -4302,5 +4302,65 @@
                           // clang-format on
                       }));
 
+class BlitTextureToBufferTest : public DawnTest {};
+
+// Test that encoding a CopyTextureToBuffer but not submitting it doesn't mark the buffer as
+// initialized.
+TEST_P(BlitTextureToBufferTest, NoSubmitDoesNotMarkInitialized) {
+    DAWN_TEST_UNSUPPORTED_IF(UsesWire());
+
+    // Create a buffer that we will use as the destination of a CopyTextureToBuffer.
+    wgpu::BufferDescriptor bufferDesc;
+    bufferDesc.size = 256;
+    bufferDesc.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
+    wgpu::Buffer buffer = device.CreateBuffer(&bufferDesc);
+
+    // Create a source texture.
+    wgpu::TextureDescriptor textureDesc;
+    textureDesc.size = {64, 1, 1};
+    textureDesc.format = wgpu::TextureFormat::R32Float;
+    textureDesc.usage = wgpu::TextureUsage::CopySrc;
+    wgpu::Texture texture = device.CreateTexture(&textureDesc);
+
+    wgpu::TexelCopyTextureInfo src = utils::CreateTexelCopyTextureInfo(texture, 0, {0, 0, 0});
+    wgpu::TexelCopyBufferInfo dst = utils::CreateTexelCopyBufferInfo(buffer, 0, 256, 1);
+    wgpu::Extent3D copySize = {64, 1, 1};
+
+    // Encode the CopyTextureToBuffer.
+    wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+    encoder.CopyTextureToBuffer(&src, &dst, &copySize);
+    // Finish the encoder, but do NOT submit the command buffer.
+    encoder.Finish();
+
+    // Now, if we use the buffer in a way that requires initialization (e.g., as a source of a
+    // copy), it should be lazy-cleared because the previous CopyTextureToBuffer was never
+    // submitted.
+    wgpu::BufferDescriptor dstBufferDesc;
+    dstBufferDesc.size = 256;
+    dstBufferDesc.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::CopySrc;
+    wgpu::Buffer dstBuffer = device.CreateBuffer(&dstBufferDesc);
+
+    wgpu::CommandEncoder encoder2 = device.CreateCommandEncoder();
+    encoder2.CopyBufferToBuffer(buffer, 0, dstBuffer, 0, 256);
+    wgpu::CommandBuffer cb = encoder2.Finish();
+
+    size_t lazyClearsBefore = native::GetLazyClearCountForTesting(device.Get());
+    queue.Submit(1, &cb);
+    size_t lazyClearsAfter = native::GetLazyClearCountForTesting(device.Get());
+
+    // If the buffer was incorrectly marked as initialized during encoding of the first command,
+    // lazyClearsAfter - lazyClearsBefore will be 0.
+    // Otherwise, it should be 1.
+    EXPECT_EQ(lazyClearsAfter - lazyClearsBefore, 1u);
+}
+
+DAWN_INSTANTIATE_TEST(BlitTextureToBufferTest,
+                      D3D11Backend({"use_blit_for_t2b"}),
+                      D3D12Backend({"use_blit_for_t2b"}),
+                      MetalBackend({"use_blit_for_t2b"}),
+                      OpenGLBackend({"use_blit_for_t2b"}),
+                      OpenGLESBackend({"use_blit_for_t2b"}),
+                      VulkanBackend({"use_blit_for_t2b"}));
+
 }  // anonymous namespace
 }  // namespace dawn
diff --git a/src/dawn/tests/end2end/TextureZeroInitTests.cpp b/src/dawn/tests/end2end/TextureZeroInitTests.cpp
index 7f6f0d7..97a5481 100644
--- a/src/dawn/tests/end2end/TextureZeroInitTests.cpp
+++ b/src/dawn/tests/end2end/TextureZeroInitTests.cpp
@@ -311,7 +311,14 @@
     wgpu::CommandBuffer commandBuffer = encoder.Finish();
 
     // Expect texture to be lazy initialized.
-    EXPECT_LAZY_CLEAR(1u, queue.Submit(1, &commandBuffer));
+    // TODO(b/513631768): SetInitialized is now skipped for use_blit_for_t2b path.
+    // If blit is used for T2B, the destination buffer is NOT marked as initialized during encoding,
+    // so it will also be lazy cleared during the first usage (which is the blit itself).
+    uint32_t expectedLazyClearCount = 1u;
+    if (HasToggleEnabled("use_blit_for_t2b")) {
+        expectedLazyClearCount++;
+    }
+    EXPECT_LAZY_CLEAR(expectedLazyClearCount, queue.Submit(1, &commandBuffer));
 
     // Expect texture subresource initialized to be true
     EXPECT_TRUE(native::IsTextureSubresourceInitialized(texture.Get(), 0, 1, 0, kArrayLayers));
Loading diff…

Original Bug Report

reported by [email protected]

WebGPU T2B Compute-Blit No-Submit Stale Buffer Disclosure

Security Bug

Important: Please do not change the component of this bug manually.

Please READ THIS FAQ before filing a bug: https://chromium.googlesource.com/chromium/src/+/HEAD/docs/security/faq.md

Please see the following link for instructions on filing security bugs: https://www.chromium.org/Home/chromium-security/reporting-security-bugs

Reports may be eligible for reward payments under the Chrome VRP: https://g.co/chrome/vrp

NOTE: Security bugs are normally made public once a fix has been widely deployed.


VULNERABILITY DETAILS

third_party/dawn/src/dawn/native/BlitTextureToBuffer.cpp marks a WebGPU destination buffer initialized while encoding a copyTextureToBuffer() command that uses Dawn’s T2B compute-blit path:

// Skip clearing the buffer if this is full size copy.
dst.buffer->SetInitialized(fullSizeCopy || dst.buffer->IsInitialized());

This is a security issue because WebGPU command buffers do not have to be submitted. A web page can call encoder.finish() and intentionally drop the returned GPUCommandBuffer. In that case the compute blit never executes and never writes the destination buffer, but Dawn’s persistent buffer metadata still says the buffer is initialized.

A later submitted copyBufferToBuffer() from the same destination buffer then skips Dawn’s lazy zero-initialization path and exposes stale GPU allocation contents to JavaScript through a MAP_READ buffer.

The broken invariant is:

BufferBase::mIsDataInitialized must become true only after the buffer contents
were actually initialized by submitted GPU work, CPU upload, map-at-creation
initialization, or allocator-level clearing.

fullSizeCopy only proves that the encoded T2B operation would overwrite the whole buffer if it executed. It does not prove that the command buffer will ever be submitted.

This is the same bug class as the timestamp resolveQuerySet no-submit issue fixed in Dawn by b073946efb / Chromium backport 36a8c01, where an analogous encode-time SetInitialized(true) call was removed because encoded work may never execute.

DEMONSTRATED IMPACT

The demonstrated impact is stale GPU allocation disclosure within one Dawn GPUDevice allocator scope when the use_blit_for_t2b path is reachable.

Script with access to a GPUDevice can recover bytes from WebGPU resources that were previously destroyed or dropped within that same GPUDevice, if it can allocate a matching buffer size class and trigger the no-submit T2B compute-blit path.

This can expose application-level GPU data such as image-processing intermediates, ML/inference tensors, render targets, texture readback buffers, or other transient WebGPU resources after the application has dropped or destroyed the original resource.

A practical attacking scenario

A same-origin third-party script (e.g. imported <script src=xxx> tags, which can be compromised analytics, ad, or CDN dependency) — that activates after the destroy step has full access to that GPUDevice but no API path to the destroyed weights or activations. This bug provides the path: allocate a matching-size buffer, no-submit T2B, read back the destroyed allocation slot.

VERSION

Chrome Version: 148.0.7778.167 Stable Operating System: Android 15, Pixel 6, ARM Mali Valhall GPU, Vulkan backend

Result:

Positive with --enable-dawn-features=use_blit_for_t2b
Negative without the Dawn T2B feature on this device
--enable-unsafe-webgpu not required

Chrome Version: 148.0.7778.168 Stable Operating System: Windows 10 Version 22H2, Build 19045.6466, NVIDIA GeForce RTX 3070 Ti, driver 32.0.15.7602

Result:

No flags: negative on this host. chrome://gpu shows the default usable WebGPU
adapter is D3D12, and the D3D11 WebGPU adapter is blocklisted. 
With --enable-dawn-features=use_blit_for_t2b: positive stale GPU buffer
disclosure.

Chrome Version: 149.0.7827.14 Beta Operating System: Windows 10 Version 22H2, Build 19045.6466, NVIDIA GeForce RTX 3070 Ti, driver 32.0.15.7602

Result:

No flags: negative on this host for the same D3D12-default / D3D11-blocklisted
reason.
With --enable-dawn-features=use_blit_for_t2b: positive stale GPU buffer
disclosure.

REPRODUCTION CASE

Android PoC verification steps

Use these steps for Android Chrome. The tested Pixel 6 path required the non-rooted Chrome command-line mechanism plus use_blit_for_t2b.

  1. In Chrome, open:
chrome://flags/#enable-command-line-on-non-rooted-devices
  1. Set it to Enabled.
  2. Relaunch Chrome.
  3. With USB debugging enabled, install the command line. From this folder on the computer, either push the provided template:
adb push chrome-command-line.txt /data/local/tmp/chrome-command-line

Then restart Chrome. 5. Set up the http server.

python3 -m http.server 18099 --bind 127.0.0.1
adb reverse tcp:18099 tcp:18099
adb shell am force-stop com.android.chrome
adb shell am start -n com.android.chrome/com.google.android.apps.chrome.Main \
  -d http://127.0.0.1:18099/poc.html

Windows PoC verification steps

The server setup procedure is the same as Android. The command line to start Chrome with certain command lines can be:

$Chrome = "$env:LOCALAPPDATA\Google\Chrome\Application\chrome.exe"
if (!(Test-Path $Chrome)) { $Chrome = "$env:ProgramFiles\Google\Chrome\Application\chrome.exe" }
& $Chrome `
  --user-data-dir="$env:TEMP\webgpu-t2b-repro-stable" `
  --no-first-run `
  --no-default-browser-check `
  --enable-dawn-features=use_blit_for_t2b `
  http://127.0.0.1:18099/poc.html

The poc.html is attached below. The PoC performs the following sequence:

1. Victim: Create one GPUDevice.
2. Victim: Create a prior WebGPU resource containing a per-trial positional nonce.
3. Victim: Destroy/drop that prior resource.
4. Victim: Submit an empty command buffer and await queue.onSubmittedWorkDone() so
   Dawn can recycle the prior allocation.
5. Attacker: Allocate an attacker buffer of the same size class.
6. Attacker: Encode a full-buffer copyTextureToBuffer() into the attacker buffer.
7. Attacker: Call encoder.finish() but do not submit that command buffer.
8. Attacker: Submit a separate copyBufferToBuffer(attacker -> MAP_READ buffer).
9. Attacker: Map the readback buffer and check for current or historical nonce bytes.

The critical bug is step 7: copyTextureToBuffer() marks the attacker buffer initialized during encoding, but the command buffer is never submitted and the attacker buffer is never actually written by the T2B blit.

How to Read poc.html

The page highlights the no-submit attack rows and keeps the full JSON under “Raw CHROVUS_LINE output”.

Important fields:

didSubmit=false
  This is the vulnerable no-submit path. The T2B command buffer was finished
  and dropped instead of being submitted.

nonZero / allZero
  A safe no-submit read should return all zeroes because the destination buffer
  should still require Dawn lazy initialization. nonZero > 0 in a no-submit
  attack trial means stale bytes were exposed.

linearMatches
  Number of texels matching the current trial pattern
  [x_low_byte, y_low_byte, nonce_byte_0, nonce_byte_1] at the expected row-major
  position. This is the strongest current-trial proof.

nonceOnly
  Number of texels containing the current nonce in bytes 2 and 3 but not at the
  expected row-major position. This still proves recovery of current-trial data,
  and usually reflects driver tiling/swizzling.

historicalNonceHits
  Matches for nonces from earlier trials. Hits in T3/T4 are useful because those
  trials do not create a current primer allocation before the no-submit read.

Trial meanings:

T1_L2_positional_4M
  Primes and destroys a 4MB texture, then performs a no-submit T2B read into a
  same-size attacker buffer. Current nonce hits are a positive leak.

T2_L2_positional_4M_spray
  Same size class as T1, with allocator spray. Current or historical nonce hits
  show stale allocation reuse.

T3_no_prime_null_4M
  No current primer is created. Historical nonce hits show residual bytes from
  an earlier trial, not from the current trial's own write.

T4_no_prime_null_4M_spray
  Same as T3, with allocator spray to exercise reuse behavior.

T5_L2_positional_256K
  Repeats the current nonce leak test in a smaller 256KB size class.

The expected positive pattern is:

Any no-submit attack row with:

  didSubmit=false
  allZero=0
  linearMatches + nonceOnly > 0

or:

  didSubmit=false
  allZero=0
  historicalNonceHits[*].hits > 0

T3 and T4 are especially useful for explaining impact because they demonstrate historical residual data without a current same-trial primer.

CREDIT INFORMATION Externally reported security bugs may appear in Chrome release notes. If this bug is included, how would you like to be credited? Reporter credit: Chrovus

View on issue tracker