Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in Dawn
DescriptionInsufficient validation of untrusted input in Dawn
ComponentDawn
Bug ClassLogic Error
Tracker513947609
Fix commite632325fb425 (dawn) +38/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
TEST_F
src/dawn/tests/unittests/validation/IndexBufferValidationTests.cpp
modified

Files Changed

  • src/dawn/native/CommandBufferStateTracker.cpp
  • src/dawn/tests/unittests/validation/IndexBufferValidationTests.cpp
From e632325fb42516c39cf4a4df11738de952171897 Mon Sep 17 00:00:00 2001
From: Corentin Wallez <[email protected]>
Date: Wed, 20 May 2026 02:42:52 -0700
Subject: [PATCH] [dawn][native] Dirty the validation aspect on SetIndexBuffer

When the CommandBufferStateTracker was introduced, there was no format
parameter to SetIndexBuffer so the validation aspect was always valid
after the first SetIndexBuffer call. However with the introduction of
the IndexFormat, we now need to dirty the aspect so that on the next
draw, the IndexFormat is checked for compatibility between the pipeline
and SetIndexBuffer.

Fixed: 513947609
Change-Id: I8c23968b44b5d090af5c8a717d4504718dfabe2e
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/309395
Reviewed-by: Brandon Jones <[email protected]>
Commit-Queue: Corentin Wallez <[email protected]>
---

diff --git a/src/dawn/native/CommandBufferStateTracker.cpp b/src/dawn/native/CommandBufferStateTracker.cpp
index ee1bc43..0d4ebc4 100644
--- a/src/dawn/native/CommandBufferStateTracker.cpp
+++ b/src/dawn/native/CommandBufferStateTracker.cpp
@@ -811,6 +811,7 @@
     mIndexFormat = format;
     mIndexBufferSize = size;
     mIndexBufferOffset = offset;
+    mAspects.reset(VALIDATION_ASPECT_INDEX_BUFFER);
 }
 
 void CommandBufferStateTracker::UnsetVertexBuffer(VertexBufferSlot slot) {
diff --git a/src/dawn/tests/unittests/validation/IndexBufferValidationTests.cpp b/src/dawn/tests/unittests/validation/IndexBufferValidationTests.cpp
index 203a63f..1942bb5 100644
--- a/src/dawn/tests/unittests/validation/IndexBufferValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/IndexBufferValidationTests.cpp
@@ -240,6 +240,43 @@
     }
 }
 
+// Check that changing the index buffer format forces new validation against the strip pipeline.
+TEST_F(IndexBufferValidationTest, IndexFormatChangesAfterFirstValidDraw) {
+    wgpu::RenderPipeline pipeline =
+        MakeTestPipeline(wgpu::IndexFormat::Uint32, wgpu::PrimitiveTopology::TriangleStrip);
+
+    wgpu::Buffer indexBuffer =
+        utils::CreateBufferFromData<uint32_t>(device, wgpu::BufferUsage::Index, {0, 1, 2});
+
+    utils::ComboRenderBundleEncoderDescriptor renderBundleDesc = {};
+    renderBundleDesc.colorFormatCount = 1;
+    renderBundleDesc.cColorFormats[0] = wgpu::TextureFormat::RGBA8Unorm;
+
+    // Control case: the strip index format matches the pipeline, then set again after a draw with
+    // the same format.
+    {
+        wgpu::RenderBundleEncoder encoder = device.CreateRenderBundleEncoder(&renderBundleDesc);
+        encoder.SetPipeline(pipeline);
+        encoder.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint32);
+        encoder.DrawIndexed(3);
+        encoder.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint32);
+        encoder.DrawIndexed(3);
+        encoder.Finish();
+    }
+
+    // Error case: the strip index format matches the pipeline, then set again after a draw with the
+    // different format this time.
+    {
+        wgpu::RenderBundleEncoder encoder = device.CreateRenderBundleEncoder(&renderBundleDesc);
+        encoder.SetPipeline(pipeline);
+        encoder.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint32);
+        encoder.DrawIndexed(3);
+        encoder.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint16);
+        encoder.DrawIndexed(3);
+        ASSERT_DEVICE_ERROR(encoder.Finish());
+    }
+}
+
 // Check that the index buffer must have the Index usage.
 TEST_F(IndexBufferValidationTest, InvalidUsage) {
     wgpu::Buffer indexBuffer =
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/src/dawn/tests/unittests/validation/IndexBufferValidationTests.cpp b/src/dawn/tests/unittests/validation/IndexBufferValidationTests.cpp
index 203a63f..1942bb5 100644
--- a/src/dawn/tests/unittests/validation/IndexBufferValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/IndexBufferValidationTests.cpp
@@ -240,6 +240,43 @@
     }
 }
 
+// Check that changing the index buffer format forces new validation against the strip pipeline.
+TEST_F(IndexBufferValidationTest, IndexFormatChangesAfterFirstValidDraw) {
+    wgpu::RenderPipeline pipeline =
+        MakeTestPipeline(wgpu::IndexFormat::Uint32, wgpu::PrimitiveTopology::TriangleStrip);
+
+    wgpu::Buffer indexBuffer =
+        utils::CreateBufferFromData<uint32_t>(device, wgpu::BufferUsage::Index, {0, 1, 2});
+
+    utils::ComboRenderBundleEncoderDescriptor renderBundleDesc = {};
+    renderBundleDesc.colorFormatCount = 1;
+    renderBundleDesc.cColorFormats[0] = wgpu::TextureFormat::RGBA8Unorm;
+
+    // Control case: the strip index format matches the pipeline, then set again after a draw with
+    // the same format.
+    {
+        wgpu::RenderBundleEncoder encoder = device.CreateRenderBundleEncoder(&renderBundleDesc);
+        encoder.SetPipeline(pipeline);
+        encoder.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint32);
+        encoder.DrawIndexed(3);
+        encoder.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint32);
+        encoder.DrawIndexed(3);
+        encoder.Finish();
+    }
+
+    // Error case: the strip index format matches the pipeline, then set again after a draw with the
+    // different format this time.
+    {
+        wgpu::RenderBundleEncoder encoder = device.CreateRenderBundleEncoder(&renderBundleDesc);
+        encoder.SetPipeline(pipeline);
+        encoder.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint32);
+        encoder.DrawIndexed(3);
+        encoder.SetIndexBuffer(indexBuffer, wgpu::IndexFormat::Uint16);
+        encoder.DrawIndexed(3);
+        ASSERT_DEVICE_ERROR(encoder.Finish());
+    }
+}
+
 // Check that the index buffer must have the Index usage.
 TEST_F(IndexBufferValidationTest, InvalidUsage) {
     wgpu::Buffer indexBuffer =
Loading diff…

Original Bug Report

reported by [email protected]

Validation bypass in Dawn's CommandBufferStateTracker due to missing aspect reset

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. 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 state tracking fails to invalidate previous validation results when the index buffer format changes. This allows WebGPU content to bypass mandatory compatibility checks between the pipeline’s strip index format and the bound index buffer. On D3D12 backends, this results in a Pipeline State Object (PSO) mismatch that violates the driver API contract.

Affected files:

  • third_party/dawn/src/dawn/native/CommandBufferStateTracker.cpp
  • third_party/dawn/src/dawn/native/RenderEncoderBase.cpp
  • third_party/dawn/src/dawn/native/d3d12/RenderPipelineD3D12.cpp
  • third_party/dawn/src/dawn/native/d3d12/CommandBufferD3D12.cpp

Estimated timestamp from git blame: Unknown (Google3 checkout)

Description

A potential vulnerability exists in Dawn’s CommandBufferStateTracker where updating the index buffer format via SetIndexBuffer fails to reset the VALIDATION_ASPECT_INDEX_BUFFER state bit. This bit is used as a ’lazy aspect’ to determine if re-validation is required before a draw call. Because the bit is not cleared when the format changes, subsequent draw calls may follow a ‘fast-path’ that assumes previous validation results remain valid, even if the new format is incompatible with the current pipeline.

Technical Details

Root Cause

In third_party/dawn/src/dawn/native/CommandBufferStateTracker.cpp, the SetIndexBuffer method updates internal state but missing a reset of the corresponding validation bit:

void CommandBufferStateTracker::SetIndexBuffer(BufferBase* buffer,
                                               wgpu::IndexFormat format,
                                               uint64_t offset,
                                               uint64_t size) {
    mIndexBuffer = buffer;
    mIndexFormat = format;
    mIndexBufferSize = size;
    mIndexBufferOffset = offset;
    // Potential Bug: mAspects.reset(VALIDATION_ASPECT_INDEX_BUFFER) is missing.
}

When ValidateOperation is called during a draw, it checks the mAspects bitset. If VALIDATION_ASPECT_INDEX_BUFFER is already set, it returns early, skipping the call to RecomputeLazyAspects where the mandatory WebGPU compatibility checks (WebGPU §10.3.6) are performed.

Potential Impact

On the D3D12 backend, the IBStripCutValue (the primitive restart sentinel) is baked into the Pipeline State Object (PSO) at creation time based on the pipeline’s stripIndexFormat. If a draw call proceeds where the bound index buffer format (e.g., 16-bit) does not match the PSO’s expected format (e.g., 32-bit), it violates the D3D12 API contract. This mismatch can lead to unexpected GPU behavior, including out-of-bounds memory access within the GPU driver or a Denial of Service (TDR).

Suggested Reproduction Steps (Potential)

  1. Create a GPURenderPipeline with primitive: { topology: 'triangle-strip', stripIndexFormat: 'uint32' }.
  2. In a render pass:
    • Call setPipeline(pipeline).
    • Call setIndexBuffer(buffer32, 'uint32') and perform an indexed draw. This successfully validates and sets the VALIDATION_ASPECT_INDEX_BUFFER bit.
    • Call setIndexBuffer(buffer16, 'uint16').
    • Perform another indexed draw.
  3. Observe that the second draw call proceeds without a validation error, despite the 16-bit format being incompatible with the pipeline’s 32-bit strip index format requirement.

In third_party/dawn/src/dawn/native/CommandBufferStateTracker.cpp, update SetIndexBuffer to reset the index buffer validation aspect:

void CommandBufferStateTracker::SetIndexBuffer(...) {
    // ... existing updates ...
    mAspects.reset(VALIDATION_ASPECT_INDEX_BUFFER);
}

Evaluated with Chrome root at commit: 1a8d40fc44df2088d5945c0bf53584038aa1614a


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.

View on issue tracker