CVE-2026-17867
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
CompatTextureViewDimensionValidationTestssrc/dawn/tests/unittests/validation/CompatValidationTests.cpp |
modified | |
ifsrc/dawn/tests/unittests/validation/CompatValidationTests.cpp |
modified | |
TEST_Psrc/dawn/tests/unittests/validation/CompatValidationTests.cpp |
modified |
Files Changed
src/dawn/native/BindGroup.cppsrc/dawn/tests/unittests/validation/CompatValidationTests.cpp
Patch
From cdcdd8bc04524ed77ee476c4137a8c4428a82c09 Mon Sep 17 00:00:00 2001
From: Stephen White <senorblanco@chromium.org>
Date: Fri, 19 Jun 2026 07:56:07 -0700
Subject: [PATCH] Fix storage texture binding validation in Compat
Storage texture bindings in Compat mode should obey the
same limitations that sampled texture bindings do: the
view and texture dimension must match.
Bug: 520527496
Change-Id: I2776ef413e69c054617fece42544991c7908e50a
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/318235
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Stephen White <senorblanco@chromium.org>
---
diff --git a/src/dawn/native/BindGroup.cpp b/src/dawn/native/BindGroup.cpp
index b8be76f..7c3f52e 100644
--- a/src/dawn/native/BindGroup.cpp
+++ b/src/dawn/native/BindGroup.cpp
@@ -199,6 +199,23 @@
return {};
}
+MaybeError ValidateTextureBindingViewDimension(DeviceBase* device,
+ TextureViewBase* view,
+ TextureBase* texture) {
+ if (!device->HasFlexibleTextureViews()) {
+ DAWN_INVALID_IF(
+ view->GetDimension() != texture->GetCompatibilityTextureBindingViewDimension(),
+ "Dimension (%s) of %s must match textureBindingViewDimension (%s) of "
+ "%s in compatibility mode.",
+ view->GetDimension(), view, texture->GetCompatibilityTextureBindingViewDimension(),
+ texture);
+
+ DAWN_TRY(ValidateCompatibilityModeTextureViewArrayLayer(device, view, texture));
+ }
+
+ return {};
+}
+
MaybeError ValidateSampledTextureBinding(DeviceBase* device,
const BindGroupEntry& entry,
const TextureBindingInfo& layout,
@@ -235,16 +252,7 @@
"Dimension (%s) of %s doesn't match the expected dimension (%s).",
entry.textureView->GetDimension(), entry.textureView, layout.viewDimension);
- if (!device->HasFlexibleTextureViews()) {
- DAWN_INVALID_IF(
- view->GetDimension() != texture->GetCompatibilityTextureBindingViewDimension(),
- "Dimension (%s) of %s must match textureBindingViewDimension (%s) of "
- "%s in compatibility mode.",
- view->GetDimension(), view, texture->GetCompatibilityTextureBindingViewDimension(),
- texture);
-
- DAWN_TRY(ValidateCompatibilityModeTextureViewArrayLayer(device, view, texture));
- }
+ DAWN_TRY(ValidateTextureBindingViewDimension(device, view, texture));
return {};
}
@@ -313,6 +321,8 @@
DAWN_TRY(ValidateCompatibilityModeTextureViewArrayLayer(device, view, texture));
}
+ DAWN_TRY(ValidateTextureBindingViewDimension(device, view, texture));
+
return {};
}
diff --git a/src/dawn/tests/unittests/validation/CompatValidationTests.cpp b/src/dawn/tests/unittests/validation/CompatValidationTests.cpp
index 2a8f315..c4a26f8 100644
--- a/src/dawn/tests/unittests/validation/CompatValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/CompatValidationTests.cpp
@@ -1716,7 +1716,7 @@
class CompatTextureViewDimensionValidationTests : public CompatTextureViewValidationTests {
protected:
- void TestBindingTextureViewDimensions(
+ void TestTextureBindingTextureViewDimensions(
const uint32_t depth,
const wgpu::TextureViewDimension textureBindingViewDimension,
const wgpu::TextureViewDimension viewDimension,
@@ -1727,8 +1727,39 @@
? wgpu::TextureViewDimension::e2D
: viewDimension}});
- wgpu::Texture texture = CreateTextureWithViewDimension(depth, wgpu::TextureDimension::e2D,
- textureBindingViewDimension);
+ wgpu::Texture texture = CreateTextureWithViewDimension(
+ depth, wgpu::TextureDimension::e2D, textureBindingViewDimension,
+ wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureUsage::TextureBinding);
+
+ wgpu::TextureViewDescriptor viewDesc = {};
+ viewDesc.dimension = viewDimension;
+
+ if (success) {
+ utils::MakeBindGroup(device, layout, {{0, texture.CreateView(&viewDesc)}});
+ } else {
+ ASSERT_DEVICE_ERROR(
+ utils::MakeBindGroup(device, layout, {{0, texture.CreateView(&viewDesc)}}),
+ testing::HasSubstr("must match textureBindingViewDimension"));
+ }
+
+ texture.Destroy();
+ }
+
+ void TestStorageBindingTextureViewDimensions(
+ const uint32_t depth,
+ const wgpu::TextureViewDimension textureBindingViewDimension,
+ const wgpu::TextureViewDimension viewDimension,
+ bool success) {
+ wgpu::BindGroupLayout layout = utils::MakeBindGroupLayout(
+ device, {{0, wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::ReadWrite,
+ wgpu::TextureFormat::R32Uint,
+ viewDimension == wgpu::TextureViewDimension::Undefined
+ ? wgpu::TextureViewDimension::e2D
+ : viewDimension}});
+
+ wgpu::Texture texture = CreateTextureWithViewDimension(
+ depth, wgpu::TextureDimension::e2D, textureBindingViewDimension,
+ wgpu::TextureFormat::R32Uint, wgpu::TextureUsage::StorageBinding);
wgpu::TextureViewDescriptor viewDesc = {};
viewDesc.dimension = viewDimension;
@@ -1751,10 +1782,14 @@
bool success,
const char* expectedSubstr) {
if (success) {
- CreateTextureWithViewDimension(depth, dimension, textureBindingViewDimension);
+ CreateTextureWithViewDimension(depth, dimension, textureBindingViewDimension,
+ wgpu::TextureFormat::RGBA8Unorm,
+ wgpu::TextureUsage::TextureBinding);
} else {
ASSERT_DEVICE_ERROR(
- CreateTextureWithViewDimension(depth, dimension, textureBindingViewDimension),
+ CreateTextureWithViewDimension(depth, dimension, textureBindingViewDimension,
+ wgpu::TextureFormat::RGBA8Unorm,
+ wgpu::TextureUsage::TextureBinding),
testing::HasSubstr(expectedSubstr));
}
}
@@ -1781,16 +1816,16 @@
wgpu::Texture CreateTextureWithViewDimension(
const uint32_t depth,
const wgpu::TextureDimension dimension,
- const wgpu::TextureViewDimension textureBindingViewDimension) {
- constexpr wgpu::TextureFormat viewFormat = wgpu::TextureFormat::RGBA8Unorm;
-
+ const wgpu::TextureViewDimension textureBindingViewDimension,
+ const wgpu::TextureFormat format,
+ wgpu::TextureUsage usage) {
wgpu::TextureDescriptor textureDesc;
textureDesc.size = {1, 1, depth};
textureDesc.dimension = dimension;
- textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
- textureDesc.usage = wgpu::TextureUsage::TextureBinding;
+ textureDesc.format = format;
+ textureDesc.usage = usage;
textureDesc.viewFormatCount = 1;
- textureDesc.viewFormats = &viewFormat;
+ textureDesc.viewFormats = &format;
wgpu::TextureBindingViewDimension textureBindingViewDimensionDesc;
@@ -1860,41 +1895,55 @@
}
TEST_P(CompatTextureViewDimensionValidationTests, OneLayerIs2DView) {
- TestBindingTextureViewDimensions(1, wgpu::TextureViewDimension::Undefined,
- wgpu::TextureViewDimension::e2D, true);
+ TestTextureBindingTextureViewDimensions(1, wgpu::TextureViewDimension::Undefined,
+ wgpu::TextureViewDimension::e2D, true);
+ TestStorageBindingTextureViewDimensions(1, wgpu::TextureViewDimension::Undefined,
+ wgpu::TextureViewDimension::e2D, true);
}
// Test 2 layer texture gets a 2d-array viewDimension
TEST_P(CompatTextureViewDimensionValidationTests, TwoLayersIs2DArrayView) {
- TestBindingTextureViewDimensions(2, wgpu::TextureViewDimension::Undefined,
- wgpu::TextureViewDimension::e2DArray, true);
+ TestTextureBindingTextureViewDimensions(2, wgpu::TextureViewDimension::Undefined,
+ wgpu::TextureViewDimension::e2DArray, true);
+ TestStorageBindingTextureViewDimensions(2, wgpu::TextureViewDimension::Undefined,
+ wgpu::TextureViewDimension::e2DArray, true);
}
// Test 6 layer texture gets a 2d-array viewDimension
TEST_P(CompatTextureViewDimensionValidationTests, SixLayersIs2DArrayView) {
- TestBindingTextureViewDimensions(6, wgpu::TextureViewDimension::Undefined,
- wgpu::TextureViewDimension::e2DArray, true);
+ TestTextureBindingTextureViewDimensions(6, wgpu::TextureViewDimension::Undefined,
+ wgpu::TextureViewDimension::e2DArray, true);
+ TestStorageBindingTextureViewDimensions(6, wgpu::TextureViewDimension::Undefined,
+ wgpu::TextureViewDimension::e2DArray, true);
}
// Test 2d texture can not be viewed as 2D array. Unless FlexibleTextureViews is enabled.
TEST_P(CompatTextureViewDimensionValidationTests, TwoDTextureViewDimensionCanNotBeViewedAs2DArray) {
Regression Test / PoC
diff --git a/src/dawn/tests/unittests/validation/CompatValidationTests.cpp b/src/dawn/tests/unittests/validation/CompatValidationTests.cpp
index 2a8f315..c4a26f8 100644
--- a/src/dawn/tests/unittests/validation/CompatValidationTests.cpp
+++ b/src/dawn/tests/unittests/validation/CompatValidationTests.cpp
@@ -1716,7 +1716,7 @@
class CompatTextureViewDimensionValidationTests : public CompatTextureViewValidationTests {
protected:
- void TestBindingTextureViewDimensions(
+ void TestTextureBindingTextureViewDimensions(
const uint32_t depth,
const wgpu::TextureViewDimension textureBindingViewDimension,
const wgpu::TextureViewDimension viewDimension,
@@ -1727,8 +1727,39 @@
? wgpu::TextureViewDimension::e2D
: viewDimension}});
- wgpu::Texture texture = CreateTextureWithViewDimension(depth, wgpu::TextureDimension::e2D,
- textureBindingViewDimension);
+ wgpu::Texture texture = CreateTextureWithViewDimension(
+ depth, wgpu::TextureDimension::e2D, textureBindingViewDimension,
+ wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureUsage::TextureBinding);
+
+ wgpu::TextureViewDescriptor viewDesc = {};
+ viewDesc.dimension = viewDimension;
+
+ if (success) {
+ utils::MakeBindGroup(device, layout, {{0, texture.CreateView(&viewDesc)}});
+ } else {
+ ASSERT_DEVICE_ERROR(
+ utils::MakeBindGroup(device, layout, {{0, texture.CreateView(&viewDesc)}}),
+ testing::HasSubstr("must match textureBindingViewDimension"));
+ }
+
+ texture.Destroy();
+ }
+
+ void TestStorageBindingTextureViewDimensions(
+ const uint32_t depth,
+ const wgpu::TextureViewDimension textureBindingViewDimension,
+ const wgpu::TextureViewDimension viewDimension,
+ bool success) {
+ wgpu::BindGroupLayout layout = utils::MakeBindGroupLayout(
+ device, {{0, wgpu::ShaderStage::Compute, wgpu::StorageTextureAccess::ReadWrite,
+ wgpu::TextureFormat::R32Uint,
+ viewDimension == wgpu::TextureViewDimension::Undefined
+ ? wgpu::TextureViewDimension::e2D
+ : viewDimension}});
+
+ wgpu::Texture texture = CreateTextureWithViewDimension(
+ depth, wgpu::TextureDimension::e2D, textureBindingViewDimension,
+ wgpu::TextureFormat::R32Uint, wgpu::TextureUsage::StorageBinding);
wgpu::TextureViewDescriptor viewDesc = {};
viewDesc.dimension = viewDimension;
@@ -1751,10 +1782,14 @@
bool success,
const char* expectedSubstr) {
if (success) {
- CreateTextureWithViewDimension(depth, dimension, textureBindingViewDimension);
+ CreateTextureWithViewDimension(depth, dimension, textureBindingViewDimension,
+ wgpu::TextureFormat::RGBA8Unorm,
+ wgpu::TextureUsage::TextureBinding);
} else {
ASSERT_DEVICE_ERROR(
- CreateTextureWithViewDimension(depth, dimension, textureBindingViewDimension),
+ CreateTextureWithViewDimension(depth, dimension, textureBindingViewDimension,
+ wgpu::TextureFormat::RGBA8Unorm,
+ wgpu::TextureUsage::TextureBinding),
testing::HasSubstr(expectedSubstr));
}
}
@@ -1781,16 +1816,16 @@
wgpu::Texture CreateTextureWithViewDimension(
const uint32_t depth,
const wgpu::TextureDimension dimension,
- const wgpu::TextureViewDimension textureBindingViewDimension) {
- constexpr wgpu::TextureFormat viewFormat = wgpu::TextureFormat::RGBA8Unorm;
-
+ const wgpu::TextureViewDimension textureBindingViewDimension,
+ const wgpu::TextureFormat format,
+ wgpu::TextureUsage usage) {
wgpu::TextureDescriptor textureDesc;
textureDesc.size = {1, 1, depth};
textureDesc.dimension = dimension;
- textureDesc.format = wgpu::TextureFormat::RGBA8Unorm;
- textureDesc.usage = wgpu::TextureUsage::TextureBinding;
+ textureDesc.format = format;
+ textureDesc.usage = usage;
textureDesc.viewFormatCount = 1;
- textureDesc.viewFormats = &viewFormat;
+ textureDesc.viewFormats = &format;
wgpu::TextureBindingViewDimension textureBindingViewDimensionDesc;
@@ -1860,41 +1895,55 @@
}
TEST_P(CompatTextureViewDimensionValidationTests, OneLayerIs2DView) {
- TestBindingTextureViewDimensions(1, wgpu::TextureViewDimension::Undefined,
- wgpu::TextureViewDimension::e2D, true);
+ TestTextureBindingTextureViewDimensions(1, wgpu::TextureViewDimension::Undefined,
+ wgpu::TextureViewDimension::e2D, true);
+ TestStorageBindingTextureViewDimensions(1, wgpu::TextureViewDimension::Undefined,
+ wgpu::TextureViewDimension::e2D, true);
}
// Test 2 layer texture gets a 2d-array viewDimension
TEST_P(CompatTextureViewDimensionValidationTests, TwoLayersIs2DArrayView) {
- TestBindingTextureViewDimensions(2, wgpu::TextureViewDimension::Undefined,
- wgpu::TextureViewDimension::e2DArray, true);
+ TestTextureBindingTextureViewDimensions(2, wgpu::TextureViewDimension::Undefined,
+ wgpu::TextureViewDimension::e2DArray, true);
+ TestStorageBindingTextureViewDimensions(2, wgpu::TextureViewDimension::Undefined,
+ wgpu::TextureViewDimension::e2DArray, true);
}
// Test 6 layer texture gets a 2d-array viewDimension
TEST_P(CompatTextureViewDimensionValidationTests, SixLayersIs2DArrayView) {
- TestBindingTextureViewDimensions(6, wgpu::TextureViewDimension::Undefined,
- wgpu::TextureViewDimension::e2DArray, true);
+ TestTextureBindingTextureViewDimensions(6, wgpu::TextureViewDimension::Undefined,
+ wgpu::TextureViewDimension::e2DArray, true);
+ TestStorageBindingTextureViewDimensions(6, wgpu::TextureViewDimension::Undefined,
+ wgpu::TextureViewDimension::e2DArray, true);
}
// Test 2d texture can not be viewed as 2D array. Unless FlexibleTextureViews is enabled.
TEST_P(CompatTextureViewDimensionValidationTests, TwoDTextureViewDimensionCanNotBeViewedAs2DArray) {
- TestBindingTextureViewDimensions(1, wgpu::TextureViewDimension::e2D,
- wgpu::TextureViewDimension::e2DArray,
- HasFlexibleTextureViews());
+ TestTextureBindingTextureViewDimensions(1, wgpu::TextureViewDimension::e2D,
+ wgpu::TextureViewDimension::e2DArray,
+ HasFlexibleTextureViews());
+ TestStorageBindingTextureViewDimensions(1, wgpu::TextureViewDimension::e2D,
+ wgpu::TextureViewDimension::e2DArray,
+ HasFlexibleTextureViews());
}
// Test 2d-array texture can not be viewed as cube. Unless FlexibleTextureViews is enabled.
TEST_P(CompatTextureViewDimensionValidationTests,
TwoDArrayTextureViewDimensionCanNotBeViewedAsCube) {
- TestBindingTextureViewDimensions(6, wgpu::TextureViewDimension::e2DArray,
- wgpu::TextureViewDimension::Cube, HasFlexibleTextureViews());
+ TestTextureBindingTextureViewDimensions(6, wgpu::TextureViewDimension::e2DArray,
+ wgpu::TextureViewDimension::Cube,
+ HasFlexibleTextureViews());
+ // Cube textures cannot be bound as storage at all; tested elsewhere.
}
// Test cube texture can not be viewed as 2d-array. Unless FlexibleTextureViews is enabled.
TEST_P(CompatTextureViewDimensionValidationTests, CubeTextureViewDimensionCanNotBeViewedAs2DArray) {
- TestBindingTextureViewDimensions(6, wgpu::TextureViewDimension::Cube,
- wgpu::TextureViewDimension::e2DArray,
- HasFlexibleTextureViews());
+ TestTextureBindingTextureViewDimensions(6, wgpu::TextureViewDimension::Cube,
+ wgpu::TextureViewDimension::e2DArray,
+ HasFlexibleTextureViews());
+ TestStorageBindingTextureViewDimensions(6, wgpu::TextureViewDimension::Cube,
+ wgpu::TextureViewDimension::e2DArray,
+ HasFlexibleTextureViews());
}
TEST_P(CompatTextureViewValidationTests, CanNotDrawDifferentAspectSameTextureSameBindGroup) {
Original Bug Report
Potential missing compatibility mode dimension-match validation in ValidateStorageTextureBinding
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 validation omission in Dawn’s storage texture binding validation potentially allows texture views with dimensions mismatching their compatibility view dimension to be bound. In compatibility mode on the OpenGL/GLES backend, this can lead to type mismatches during GL image binding, resulting in driver-level undefined behavior and potential GPU out-of-bounds memory access.
Affected files:
third_party/dawn/src/dawn/native/BindGroup.cpp
Estimated timestamp from git blame: 2023-11-06
Technical Details
In WebGPU compatibility mode (such as on the OpenGL/GLES backend), DeviceBase::HasFlexibleTextureViews() returns false. This requires that the dimension of a texture view used in a bind group match the resolved compatibility texture binding view dimension of its parent texture.
While ValidateSampledTextureBinding (third_party/dawn/src/dawn/native/BindGroup.cpp:238-245) strictly validates this dimension-match condition:
if (!device->HasFlexibleTextureViews()) {
DAWN_INVALID_IF(
view->GetDimension() != texture->GetCompatibilityTextureBindingViewDimension(),
"Dimension (%s) of %s must match textureBindingViewDimension (%s) of "
"%s in compatibility mode.",
view->GetDimension(), view, texture->GetCompatibilityTextureBindingViewDimension(),
texture);
DAWN_TRY(ValidateCompatibilityModeTextureViewArrayLayer(device, view, texture));
}
the companion function ValidateStorageTextureBinding (third_party/dawn/src/dawn/native/BindGroup.cpp:311-314) only invokes ValidateCompatibilityModeTextureViewArrayLayer and completely omits the GetCompatibilityTextureBindingViewDimension matching check:
if (!device->HasFlexibleTextureViews()) {
DAWN_TRY(ValidateCompatibilityModeTextureViewArrayLayer(device, view, texture));
}
Potential Impact and Mechanism
On the OpenGL backend, texture targets are determined by the compatibility texture binding view dimension during creation. When a texture view is created in compatibility mode, RequiresCreatingNewTextureView (third_party/dawn/src/dawn/native/opengl/TextureGL.cpp:85-87) always returns false, causing the view to reuse the parent texture’s underlying GL handle.
An attacker could potentially exploit this validation omission via the following steps:
- Create a 2D texture with 6 array layers,
usage = GPUTextureUsage.STORAGE_BINDING, andtextureBindingViewDimension = 'cube'. This results in the allocation of a physicalGL_TEXTURE_CUBE_MAPresource. - Create a texture view with
dimension = '2d-array'andarrayLayerCount = 6. This view reuses the underlyingGL_TEXTURE_CUBE_MAPhandle. - Create a bind group with a storage texture entry expecting a
2d-arrayview, passing the above view. This bypasses validation because of the missing check inValidateStorageTextureBinding. - Execute a compute shader that accesses the resource as
texture_storage_2d_array(image2DArrayin GLES GLSL).
When dispatching commands, CommandBufferGL.cpp:497-500 invokes BindImageTexture to bind the physical GL_TEXTURE_CUBE_MAP texture to an image unit that the shader executes operations against using image2DArray instructions. Per OpenGL ES 3.1 Specification (§8.22), binding mismatching texture targets to image uniforms results in undefined instruction execution. On unhardened GPU drivers, this could potentially lead to out-of-bounds memory writes or a GPU process crash.
Note: These steps are suggested based on static code analysis. Our tooling does not currently support running dynamic execution to provide a working proof-of-concept.
Suggested Fix
To fix this vulnerability, add the missing dimension-match validation check to ValidateStorageTextureBinding in third_party/dawn/src/dawn/native/BindGroup.cpp to ensure parity with ValidateSampledTextureBinding:
if (!device->HasFlexibleTextureViews()) {
DAWN_INVALID_IF(
view->GetDimension() != texture->GetCompatibilityTextureBindingViewDimension(),
"Dimension (%s) of %s must match textureBindingViewDimension (%s) of "
"%s in compatibility mode.",
view->GetDimension(), view, texture->GetCompatibilityTextureBindingViewDimension(),
texture);
DAWN_TRY(ValidateCompatibilityModeTextureViewArrayLayer(device, view, texture));
}
Evaluated with Chrome root at commit: e9507a33bb4148ee071aaaf8a7e9ad68770359bf
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.