CVE-2026-7973
Overview
Background
- Dawn
- Chromium’s cross-platform WebGPU implementation; its
d3d12backend translates WebGPU resources into Direct3D 12 objects in the GPU process. - `D3D12_RESOURCE_DESC::DepthOrArraySize`
- a
UINT16field holding the array-layer (or depth) count of a texture resource; its maximum representable value is65535. - `d3d12_allocate_extra_memory_for_2d_array_color_texture`
- an Intel Gen12 workaround toggle that appends extra array layers to 2D-array color textures to sidestep a driver bug.
- `ComputeExtraArraySizeForIntelGen12`
- helper that returns the number of extra array layers to append under the workaround; its result is summed with the app-requested layer count.
Root Cause Analysis
Under the Intel Gen12 workaround, dawn::native::d3d12::ResourceAllocatorManager::AllocateMemory computed revisedDescriptor.DepthOrArraySize += ComputeExtraArraySizeForIntelGen12(...) directly into a UINT16 field, and ComputeExtraArraySizeForIntelGen12 itself returned a uint32_t. When the app-requested array-layer count plus the extra layers exceeded 65535, the sum wrapped modulo 2^16 inside the UINT16 DepthOrArraySize. The driver then allocated a texture with far fewer array layers than the WebGPU app and its shaders believed existed. Because Dawn’s view/binding logic still treated the texture as having the full requested layer count, later access to the “missing” upper layers targeted memory outside the actual allocation.
The fix widens ComputeExtraArraySizeForIntelGen12 to return uint64_t, computes the sum in a uint64_t temporary, and returns DAWN_OUT_OF_MEMORY_ERROR when it would reach numeric_limits<UINT16>::max(), so the wrapped, undersized allocation is never created.
DepthOrArraySize still describes a valid, allocatable resource, so no D3D12 error surfaces and the mismatch between believed and actual layer count only manifests later as out-of-bounds access. Widening the arithmetic to uint64_t before the range check is essential because performing the check in the same narrow type that overflows cannot detect the wrap.Attack Path
-
On a machine with an Intel Gen12 GPU where
d3d12_allocate_extra_memory_for_2d_array_color_textureis enabled, a malicious page requests a WebGPU 2D-array color texture with a large layer count (e.g.8192-wideR16Uintwith1793layers). -
AllocateMemoryaddsComputeExtraArraySizeForIntelGen12(...)to the layer count so the true sum exceeds65535. -
The sum is truncated by the
UINT16DepthOrArraySize, and D3D12 allocates a texture with far fewer array layers than requested. - Shaders or copy operations reference the upper array layers that Dawn believes exist but were never allocated, producing reads/writes outside the resource.
- The out-of-bounds access corrupts GPU-process memory, giving the attacker a primitive toward escaping the renderer or GPU sandbox.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
TextureCorruptionTests_WidthAndHeight_IntelGen12src/dawn/tests/end2end/TextureCorruptionTests.cpp |
modified | |
TEST_Psrc/dawn/tests/end2end/TextureCorruptionTests.cpp |
modified | |
TextureCorruptionTests_ArrayLayersrc/dawn/tests/end2end/TextureCorruptionTests.cpp |
modified |
Files Changed
src/dawn/native/d3d12/ResourceAllocatorManagerD3D12.cppsrc/dawn/tests/end2end/TextureCorruptionTests.cpp
Audit Directions
- Narrow-type descriptor fieldsaudit every write into
UINT16/UINT8D3D12/Vulkan/Metal descriptor fields (DepthOrArraySize,MipLevels, sample counts) for sums or products that can exceed the field width, especially where a workaround adds to an app-controlled value. - Compound-assignment overflowflag
+=/*=on narrow integer members fed by helper functions; verify the arithmetic is performed in a width that cannot wrap and is range-checked before the store. - Believed-vs-allocated size mismatchtrace paths where Dawn tracks a logical dimension (layer/mip count) separately from the value handed to the driver, and confirm both are clamped or rejected together so views never index beyond the real allocation.
Patch
From cacb2e67d9cd469074052e537a7e217ac9d6f0c0 Mon Sep 17 00:00:00 2001
From: Antonio Maiorano <amaiorano@google.com>
Date: Wed, 01 Apr 2026 12:32:09 -0700
Subject: [PATCH] [d3d12][native] Fix potential uint16 overflow during texture allocation on Intel Gen12
When "d3d12_allocate_extra_memory_for_2d_array_color_texture" is
enabled, it's possible to create a texture that results in a uint16
overflow. Detect this and return an OOM.
Bug: 497565944
Change-Id: Ifeb1b9818db676ac3a990f3f965346f8201fd17a
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/300576
Commit-Queue: Antonio Maiorano <amaiorano@google.com>
Reviewed-by: Kai Ninomiya <kainino@chromium.org>
---
diff --git a/src/dawn/native/d3d12/ResourceAllocatorManagerD3D12.cpp b/src/dawn/native/d3d12/ResourceAllocatorManagerD3D12.cpp
index 7b52c67..7b61a99 100644
--- a/src/dawn/native/d3d12/ResourceAllocatorManagerD3D12.cpp
+++ b/src/dawn/native/d3d12/ResourceAllocatorManagerD3D12.cpp
@@ -191,7 +191,7 @@
return Align(columnPitch, 4);
}
-uint32_t ComputeExtraArraySizeForIntelGen12(uint32_t width,
+uint64_t ComputeExtraArraySizeForIntelGen12(uint32_t width,
uint32_t height,
uint32_t arrayLayerCount,
uint32_t mipLevelCount,
@@ -355,10 +355,18 @@
// Multisample textures have one layer at most. Only non-multisample textures need the
// workaround.
DAWN_ASSERT(revisedDescriptor.SampleDesc.Count <= 1);
- revisedDescriptor.DepthOrArraySize += ComputeExtraArraySizeForIntelGen12(
- resourceDescriptor.Width, resourceDescriptor.Height,
- resourceDescriptor.DepthOrArraySize, resourceDescriptor.MipLevels,
- resourceDescriptor.SampleDesc.Count, colorFormatBytesPerBlock);
+ // Make sure the result fits in DepthOrArraySize which is a UINT16
+ uint64_t depthOrArraySize =
+ revisedDescriptor.DepthOrArraySize +
+ ComputeExtraArraySizeForIntelGen12(
+ resourceDescriptor.Width, resourceDescriptor.Height,
+ resourceDescriptor.DepthOrArraySize, resourceDescriptor.MipLevels,
+ resourceDescriptor.SampleDesc.Count, colorFormatBytesPerBlock);
+ if (depthOrArraySize >= std::numeric_limits<UINT16>::max()) {
+ return DAWN_OUT_OF_MEMORY_ERROR(
+ "Texture array size with Intel Gen12 workaround exceeds UINT16");
+ }
+ revisedDescriptor.DepthOrArraySize = depthOrArraySize;
}
// TODO(crbug.com/dawn/849): Conditionally disable sub-allocation.
diff --git a/src/dawn/tests/end2end/TextureCorruptionTests.cpp b/src/dawn/tests/end2end/TextureCorruptionTests.cpp
index e172910..e19de3e 100644
--- a/src/dawn/tests/end2end/TextureCorruptionTests.cpp
+++ b/src/dawn/tests/end2end/TextureCorruptionTests.cpp
@@ -399,6 +399,40 @@
{kDefaultSampleCount},
{kDefaultWriteType});
+// Test for UINT16 overflow when calling ComputeExtraArraySizeForIntelGen12 as per
+// crbug.com/497565944.
+class TextureCorruptionTests_WidthAndHeight_IntelGen12 : public TextureCorruptionTests {
+ void GetRequiredLimits(const dawn::utils::ComboLimits& supported,
+ dawn::utils::ComboLimits& required) override {
+ required.maxTextureArrayLayers = supported.maxTextureArrayLayers;
+ }
+};
+TEST_P(TextureCorruptionTests_WidthAndHeight_IntelGen12, Tests) {
+ wgpu::Limits limits;
+ device.GetLimits(&limits);
+ DAWN_SUPPRESS_TEST_IF(limits.maxTextureArrayLayers < 2048);
+
+ uint32_t width = GetParam().mTextureWidth;
+ uint32_t height = GetParam().mTextureHeight;
+ uint32_t depthOrArrayLayerCount = GetParam().mArrayLayerCount;
+ uint32_t mipLevelCount = GetParam().mMipLevelCount;
+ uint32_t sampleCount = GetParam().mSampleCount;
+ wgpu::Extent3D textureSize = {width, height, depthOrArrayLayerCount};
+ wgpu::TextureFormat format = GetParam().mTextureFormat;
+ // This should fail due to an OOM error.
+ ASSERT_DEVICE_ERROR(wgpu::Texture texture =
+ Create2DTexture(textureSize, format, mipLevelCount, sampleCount));
+}
+DAWN_INSTANTIATE_TEST_P(TextureCorruptionTests_WidthAndHeight_IntelGen12,
+ {D3D12Backend({"d3d12_allocate_extra_memory_for_2d_array_color_texture"})},
+ {wgpu::TextureFormat::R16Uint},
+ {1u},
+ {8192u},
+ {1793u},
+ {kDefaultMipLevelCount},
+ {kDefaultSampleCount},
+ {WriteType::WriteTexture});
+
class TextureCorruptionTests_ArrayLayer : public TextureCorruptionTests {};
TEST_P(TextureCorruptionTests_ArrayLayer, Tests) {
Regression Test / PoC
diff --git a/src/dawn/tests/end2end/TextureCorruptionTests.cpp b/src/dawn/tests/end2end/TextureCorruptionTests.cpp
index e172910..e19de3e 100644
--- a/src/dawn/tests/end2end/TextureCorruptionTests.cpp
+++ b/src/dawn/tests/end2end/TextureCorruptionTests.cpp
@@ -399,6 +399,40 @@
{kDefaultSampleCount},
{kDefaultWriteType});
+// Test for UINT16 overflow when calling ComputeExtraArraySizeForIntelGen12 as per
+// crbug.com/497565944.
+class TextureCorruptionTests_WidthAndHeight_IntelGen12 : public TextureCorruptionTests {
+ void GetRequiredLimits(const dawn::utils::ComboLimits& supported,
+ dawn::utils::ComboLimits& required) override {
+ required.maxTextureArrayLayers = supported.maxTextureArrayLayers;
+ }
+};
+TEST_P(TextureCorruptionTests_WidthAndHeight_IntelGen12, Tests) {
+ wgpu::Limits limits;
+ device.GetLimits(&limits);
+ DAWN_SUPPRESS_TEST_IF(limits.maxTextureArrayLayers < 2048);
+
+ uint32_t width = GetParam().mTextureWidth;
+ uint32_t height = GetParam().mTextureHeight;
+ uint32_t depthOrArrayLayerCount = GetParam().mArrayLayerCount;
+ uint32_t mipLevelCount = GetParam().mMipLevelCount;
+ uint32_t sampleCount = GetParam().mSampleCount;
+ wgpu::Extent3D textureSize = {width, height, depthOrArrayLayerCount};
+ wgpu::TextureFormat format = GetParam().mTextureFormat;
+ // This should fail due to an OOM error.
+ ASSERT_DEVICE_ERROR(wgpu::Texture texture =
+ Create2DTexture(textureSize, format, mipLevelCount, sampleCount));
+}
+DAWN_INSTANTIATE_TEST_P(TextureCorruptionTests_WidthAndHeight_IntelGen12,
+ {D3D12Backend({"d3d12_allocate_extra_memory_for_2d_array_color_texture"})},
+ {wgpu::TextureFormat::R16Uint},
+ {1u},
+ {8192u},
+ {1793u},
+ {kDefaultMipLevelCount},
+ {kDefaultSampleCount},
+ {WriteType::WriteTexture});
+
class TextureCorruptionTests_ArrayLayer : public TextureCorruptionTests {};
TEST_P(TextureCorruptionTests_ArrayLayer, Tests) {
Original Bug Report
Potential GPU OOB Access via Integer Overflow in Dawn D3D12 Texture Allocation Workaround
Project Fortify, an experimental security project, has identified the following potential security issue.
Overview: An integer overflow exists in Dawn’s D3D12 backend when applying a memory allocation workaround for Intel Gen12 GPUs. A 32-bit padding size is added to a 16-bit texture dimension field without bounds checking, causing the allocation of an undersized GPU buffer. Dawn’s validation relies on the original requested size, allowing WebGPU commands to perform out-of-bounds reads and writes in GPU memory.
Affected files:
third_party/dawn/src/dawn/native/d3d12/ResourceAllocatorManagerD3D12.cppthird_party/dawn/src/dawn/native/d3d12/TextureD3D12.cppthird_party/dawn/src/dawn/native/Texture.cppthird_party/dawn/src/dawn/native/d3d12/UtilsD3D12.cpp
Estimated timestamp from git blame: 2022-12-14
Description
A potential vulnerability exists in Dawn’s D3D12 backend where an integer overflow can lead to the allocation of undersized GPU textures on systems with Intel Gen12 GPUs (e.g., Tiger Lake) running older drivers.
In third_party/dawn/src/dawn/native/d3d12/ResourceAllocatorManagerD3D12.cpp, the AllocateMemory function implements a workaround for a legacy bug in Intel Gen12 GPU drivers (Toggle::D3D12AllocateExtraMemoryFor2DArrayColorTexture). This workaround increases the number of layers in a 2D array texture to avoid corruption:
// ResourceAllocatorManagerD3D12.cpp
revisedDescriptor.DepthOrArraySize += ComputeExtraArraySizeForIntelGen12(
resourceDescriptor.Width, resourceDescriptor.Height,
resourceDescriptor.DepthOrArraySize, resourceDescriptor.MipLevels,
resourceDescriptor.SampleDesc.Count, colorFormatBytesPerBlock);
The vulnerability occurs because ComputeExtraArraySizeForIntelGen12 returns a uint32_t, but revisedDescriptor.DepthOrArraySize is a UINT16 field (as defined in the D3D12_RESOURCE_DESC struct). There are no bounds or overflow checks on this addition.
An attacker can request a texture with dimensions that cause ComputeExtraArraySizeForIntelGen12 to return a very large value. When added to the requested DepthOrArraySize, the resulting sum overflows the 16-bit integer and is truncated. Consequently, D3D12 allocates a resource with a much smaller capacity than requested. However, Dawn’s internal representation (TextureBase::mBaseSize) retains the original, larger size. When the attacker accesses layers beyond the allocated capacity, Dawn’s frontend validation permits the access, passing the out-of-bounds subresource index to the D3D12 runtime, which performs an out-of-bounds memory access in the GPU heap.
Potential Attacker Steps
Note: These steps are theoretical as our tooling agent does not run exploit code directly.
- Meet Preconditions: The victim must be using an Intel Gen12 GPU with a Windows driver older than 30.0.101.1692, triggering the workaround toggle.
- Request Malicious Texture: The attacker requests a 2D array texture via WebGPU. For example, a texture of size
1x8192with1793layers and formatr16uint.- Dawn validates this against the device’s
maxTextureArrayLayerslimit (usually 2048), which passes.
- Dawn validates this against the device’s
- Trigger Overflow: Inside
AllocateMemory,ComputeExtraArraySizeForIntelGen12calculates the required padding. For the above dimensions, it returns457,215.- The addition
1793 + 457215 = 459008overflows theUINT16type. - The value wraps around modulo 65536:
459008 % 65536 = 256.
- The addition
- Undersized Allocation: D3D12 creates a physical GPU resource with only 256 layers, but Dawn’s internal state continues to track it as having 1793 layers.
- Out-of-Bounds Access: The attacker invokes a WebGPU copy command (e.g.,
copyTextureToBuffer) targeting layer1700. Dawn’s validation permits this (1700 < 1793). - Memory Corruption: Dawn issues an
ID3D12GraphicsCommandList::CopyTextureRegioncommand for subresource1700. The GPU driver computes the memory address, pointing far past the allocated 256 layers, resulting in an out-of-bounds read/write in GPU memory (a GPU process sandbox escape primitive).
Suggested Fix
Check for integer overflow before assigning the value to the UINT16 field. If the required size exceeds the capacity of a UINT16, the allocation should fail gracefully (return an out-of-memory error).
uint32_t extraSize = ComputeExtraArraySizeForIntelGen12(...);
uint32_t newSize = resourceDescriptor.DepthOrArraySize + extraSize;
if (newSize > std::numeric_limits<UINT16>::max()) {
return DAWN_OUT_OF_MEMORY_ERROR("Texture array size with Intel Gen12 workaround exceeds UINT16 max");
}
revisedDescriptor.DepthOrArraySize = static_cast<UINT16>(newSize);
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.