Overview

Medium
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInteger overflow in Dawn
DescriptionInteger overflow in Dawn
ComponentDawn
Sub-componentD3d12
Bug ClassInteger Overflow
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker497565944
Fix commitcacb2e67d9cd (dawn) +47/-5
CWECWE-472
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-03-29 (per vendor advisory)
Fix landed2026-04-01 (public git)
Disclosed2026-05-05
Patch gap34 days — fix public in source before the advisory

Background

Dawn
Chromium’s cross-platform WebGPU implementation; its d3d12 backend translates WebGPU resources into Direct3D 12 objects in the GPU process.
`D3D12_RESOURCE_DESC::DepthOrArraySize`
a UINT16 field holding the array-layer (or depth) count of a texture resource; its maximum representable value is 65535.
`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.

Key insight
The overflow is silent: the truncated 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

  1. On a machine with an Intel Gen12 GPU where d3d12_allocate_extra_memory_for_2d_array_color_texture is enabled, a malicious page requests a WebGPU 2D-array color texture with a large layer count (e.g. 8192-wide R16Uint with 1793 layers).
  2. AllocateMemory adds ComputeExtraArraySizeForIntelGen12(...) to the layer count so the true sum exceeds 65535.
  3. The sum is truncated by the UINT16 DepthOrArraySize, and D3D12 allocates a texture with far fewer array layers than requested.
  4. Shaders or copy operations reference the upper array layers that Dawn believes exist but were never allocated, producing reads/writes outside the resource.
  5. The out-of-bounds access corrupts GPU-process memory, giving the attacker a primitive toward escaping the renderer or GPU sandbox.

Impact Assessment

Successful exploitation causes out-of-bounds reads and writes in the GPU process, i.e. memory corruption with attacker-influenced layout, a strong primitive for privilege escalation beyond the sandbox. The bug is reachable only on Intel Gen12 hardware with the specific workaround enabled, narrowing the affected population but remaining fully web-reachable via ordinary WebGPU calls.

Changed Functions

FunctionChangeNotes
TextureCorruptionTests_WidthAndHeight_IntelGen12
src/dawn/tests/end2end/TextureCorruptionTests.cpp
modified
TEST_P
src/dawn/tests/end2end/TextureCorruptionTests.cpp
modified
TextureCorruptionTests_ArrayLayer
src/dawn/tests/end2end/TextureCorruptionTests.cpp
modified

Files Changed

  • src/dawn/native/d3d12/ResourceAllocatorManagerD3D12.cpp
  • src/dawn/tests/end2end/TextureCorruptionTests.cpp

Audit Directions

  • Narrow-type descriptor fields
    audit every write into UINT16/UINT8 D3D12/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 overflow
    flag +=/*= 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 mismatch
    trace 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.
From cacb2e67d9cd469074052e537a7e217ac9d6f0c0 Mon Sep 17 00:00:00 2001
From: Antonio Maiorano <[email protected]>
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 <[email protected]>
Reviewed-by: Kai Ninomiya <[email protected]>
---

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

Regression Test / PoC

shipped with the fix
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) {
Loading diff…

Original Bug Report

reported by [email protected]

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

  1. 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.
  2. Request Malicious Texture: The attacker requests a 2D array texture via WebGPU. For example, a texture of size 1x8192 with 1793 layers and format r16uint.
    • Dawn validates this against the device’s maxTextureArrayLayers limit (usually 2048), which passes.
  3. Trigger Overflow: Inside AllocateMemory, ComputeExtraArraySizeForIntelGen12 calculates the required padding. For the above dimensions, it returns 457,215.
    • The addition 1793 + 457215 = 459008 overflows the UINT16 type.
    • The value wraps around modulo 65536: 459008 % 65536 = 256.
  4. 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.
  5. Out-of-Bounds Access: The attacker invokes a WebGPU copy command (e.g., copyTextureToBuffer) targeting layer 1700. Dawn’s validation permits this (1700 < 1793).
  6. Memory Corruption: Dawn issues an ID3D12GraphicsCommandList::CopyTextureRegion command for subresource 1700. 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.

View on issue tracker