CVE-2026-76036
Overview
Background
- Dawn
- Chromium’s native implementation of the WebGPU API, translating WebGPU calls into backend graphics APIs such as Vulkan.
- Mipmap
- a precomputed chain of progressively halved-resolution copies of a texture, where each mip level’s dimensions are derived from the base
size.widthandsize.height. - NPOT (non-power-of-two)
- a texture whose width or height is not a power of two, which can make per-mip-level size computation ambiguous under buggy driver rounding rules.
- Toggle
- a Dawn feature/workaround flag (declared in
Toggles.h, described inToggles.cpp) that can be enabled per-device to change validation or backend behavior.
Root Cause Analysis
On the PowerVR/Imagination (ImgTec) proprietary Vulkan driver, the code path that creates a depth/stencil texture with mipLevelCount > 1 and non-power-of-two dimensions triggers a driver-side miscomputation of each mip level’s size. Because the driver derives smaller mip extents differently than Dawn and the WebGPU spec expect, subsequent operations (such as queue.writeTexture to a lower mip, exercised by QueueWriteTextureTests::TextureWriteToMip) address memory using mismatched dimensions, producing an out-of-bounds buffer overflow. Dawn’s texture validation in ValidateTextureDescriptor previously only checked that depth/stencil textures were 2D and did not reject the NPOT-plus-mipmaps combination on affected hardware.
The fix introduces the VulkanDisallowNPOTDepthStencilMipmaps toggle, defaulted on for Imagination proprietary devices, and adds a DAWN_INVALID_IF guard that rejects the dangerous descriptor before any allocation occurs, so the miscomputed mip sizes are never reached.
Attack Path
- Reach texture creation
A web page uses the WebGPU API to call
device.createTexturewith a depth or stencil format on a device backed by the Imagination proprietary Vulkan driver. - Request NPOT mipmaps
The descriptor specifies non-power-of-two
size.width/size.heighttogether withmipLevelCount > 1, which pre-patch validation accepts. - Trigger miscomputed mip sizes The PowerVR driver computes the extents of the smaller mip levels incorrectly, leaving Dawn’s and the driver’s notions of each mip’s size out of sync.
- Overflow the buffer
A subsequent write or copy to one of the mismatched mip levels (e.g. via
queue.writeTexture) reads or writes past the true allocation bounds, causing the buffer overflow.
Impact Assessment
ImgTec) devices. The preconditions are that the victim uses hardware backed by the Imagination proprietary Vulkan driver and the page can issue a depth/stencil texture creation with NPOT dimensions and multiple mip levels, memory corruption that can serve as a step toward sandbox escape or further exploitation.Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Psrc/dawn/tests/end2end/QueueTests.cpp |
modified |
Files Changed
webgpu-cts/expectations.txtsrc/dawn/native/Toggles.hsrc/dawn/tests/end2end/QueueTests.cppsrc/dawn/native/vulkan/PhysicalDeviceVk.cppsrc/dawn/native/Toggles.cppsrc/dawn/native/Texture.cpp
Audit Directions
- Driver-specific size mathReview other backend code paths that rely on drivers to derive per-mip or per-array-layer extents for NPOT resources, since the same miscomputation may exist for color formats or 3D textures.
- Validation gaps by hardwareSearch Dawn validation for descriptor combinations accepted globally but known-buggy on a specific vendor, where a targeted toggle-guarded
DAWN_INVALID_IFmay be missing. - Mip-addressed transfersAudit
writeTexture,copyTextureToTexture, and similar operations that index into individual mip levels, verifying that Dawn’s computed extents match backend assumptions before memory is accessed.
Patch
From 178fb7048ad322e4ff797210358ed05254289e0c Mon Sep 17 00:00:00 2001
From: Kai Ninomiya <kainino@chromium.org>
Date: Thu, 30 Jul 2026 16:20:14 -0700
Subject: [PATCH] [dawn][vulkan] Disallow NPOT mipmapped depth/stencil textures on ImgTec
Can't think of a way to work around this, so just disable it.
Fixes: 540087398
Cq-Include-Trybots: luci.chromium.try:android-dawn-arm64-p10-rel
Change-Id: I22ce9c18564d338c9ee15e18cfe4598bc34cda95
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/328455
Reviewed-by: Brandon Jones <bajones@chromium.org>
Commit-Queue: Kai Ninomiya <kainino@chromium.org>
---
diff --git a/src/dawn/native/Texture.cpp b/src/dawn/native/Texture.cpp
index eb3b0ee..23c6752 100644
--- a/src/dawn/native/Texture.cpp
+++ b/src/dawn/native/Texture.cpp
@@ -875,6 +875,15 @@
(format->aspects & (Aspect::Depth | Aspect::Stencil)),
"The dimension (%s) of a texture with a depth/stencil format (%s) is not 2D.",
descriptor->dimension, format->format);
+ if (device->IsToggleEnabled(Toggle::VulkanDisallowNPOTDepthStencilMipmaps)) {
+ DAWN_INVALID_IF(
+ (format->aspects & (Aspect::Depth | Aspect::Stencil)) &&
+ descriptor->mipLevelCount > 1 &&
+ (!IsPowerOfTwo(descriptor->size.width) || !IsPowerOfTwo(descriptor->size.height)),
+ "Non-power-of-two depth/stencil texture (%s) with mipLevelCount (%u) > 1 is "
+ "disallowed on this device due to a driver bug.",
+ descriptor->size, descriptor->mipLevelCount);
+ }
DAWN_TRY(ValidateTextureSize(device, *descriptor, format));
return {};
diff --git a/src/dawn/native/Toggles.cpp b/src/dawn/native/Toggles.cpp
index b553e64..9dc2218 100644
--- a/src/dawn/native/Toggles.cpp
+++ b/src/dawn/native/Toggles.cpp
@@ -819,6 +819,11 @@
{"vulkan_replace_workgroup_atomic_store_with_exchange",
"Workaround for a driver bug that implements atomic store incorrectly.",
"https://crbug.com/487773864", ToggleStage::Device}},
+ {Toggle::VulkanDisallowNPOTDepthStencilMipmaps,
+ {"vulkan_disallow_npot_depth_stencil_mipmaps",
+ "Reject NPOT depth/stencil textures with mipLevelCount > 1. Workaround for mip level "
+ "miscomputation in PowerVR proprietary driver.",
+ "https://crbug.com/540087398", ToggleStage::Device}},
{Toggle::WaitIsThreadSafe,
{"wait_is_thread_safe",
"WaitFor* functions are thread-safe and can be called without the device-lock if implicit "
diff --git a/src/dawn/native/Toggles.h b/src/dawn/native/Toggles.h
index 52115d1..dea360c 100644
--- a/src/dawn/native/Toggles.h
+++ b/src/dawn/native/Toggles.h
@@ -195,6 +195,7 @@
VulkanSleepAfterLostDeviceWait,
UseSpirvReconvergenceMode,
VulkanReplaceWorkgroupAtomicStoreWithExchange,
+ VulkanDisallowNPOTDepthStencilMipmaps,
// Once all backends have been updated to be thread safe for waiting, we can remove this toggle.
WaitIsThreadSafe,
diff --git a/src/dawn/native/vulkan/PhysicalDeviceVk.cpp b/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
index caf6fbf..a91b535 100644
--- a/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
+++ b/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
@@ -1073,8 +1073,12 @@
}
if (MayBeImaginationProprietary()) {
- // crbug.com/443906252: Polyfill for case switch with large ranges.
+ // crbug.com/443906252 - Polyfill for case switch with large ranges.
deviceToggles->Default(Toggle::VulkanPolyfillSwitchWithIf, true);
+
+ // crbug.com/540087398 - Driver bug miscomputes mip sizes for NPOT depth/stencil textures.
+ // TODO(https://crbug.com/540087398): Limit this to old drivers once there's a driver fix.
+ deviceToggles->Default(Toggle::VulkanDisallowNPOTDepthStencilMipmaps, true);
}
// AMD Mesa front end optimizer bug for unary negation and abs.
diff --git a/webgpu-cts/expectations.txt b/webgpu-cts/expectations.txt
index 11b29ed..36a28cd 100644
--- a/webgpu-cts/expectations.txt
+++ b/webgpu-cts/expectations.txt
@@ -1879,6 +1879,11 @@
crbug.com/538691038 [ intel mac ] webgpu:shader,validation,functions,alias_analysis:two_pointers_to_struct_members_indirect:address_space="private";member="a";aliased=false [ Failure ]
crbug.com/538691038 [ intel mac ] webgpu:shader,validation,functions,alias_analysis:two_pointers_to_struct_members_indirect:address_space="private";member="a";aliased=true [ Failure ]
+crbug.com/540087398 [ android-pixel-10 ] webgpu:api,validation,createTexture:mipLevelCount,format:dimension="*";format="depth*" [ Failure ]
+crbug.com/540087398 [ android-pixel-10 ] webgpu:api,validation,createTexture:mipLevelCount,format:dimension="*";format="stencil*" [ Failure ]
+crbug.com/540087398 [ android-pixel-10 ] webgpu:shader,execution,expression,call,builtin,texture_utils:createTextureWithRandomDataAndGetTexels_with_generator:format="depth*";viewDimension="*" [ Failure ]
+crbug.com/540087398 [ android-pixel-10 ] webgpu:shader,execution,expression,call,builtin,texture_utils:createTextureWithRandomDataAndGetTexels_with_generator:format="stencil*";viewDimension="*" [ Failure ]
+
################################################################################
# Autogenerated Failure expectations. Please triage.
# ##ROLLER_AUTOGENERATED_FAILURES##
diff --git a/src/dawn/tests/end2end/QueueTests.cpp b/src/dawn/tests/end2end/QueueTests.cpp
index e09c8f8..5c5476a 100644
--- a/src/dawn/tests/end2end/QueueTests.cpp
+++ b/src/dawn/tests/end2end/QueueTests.cpp
@@ -546,6 +546,9 @@
// Test writing to varying mips
TEST_P(QueueWriteTextureTests, TextureWriteToMip) {
+ // TODO(crbug.com/540087398): NPOT mipmapped depth/stencil textures disallowed due to driver bug
+ DAWN_SUPPRESS_TEST_IF(IsImgTec());
+
constexpr uint32_t kWidth = 259;
constexpr uint32_t kHeight = 127;
Regression Test / PoC
diff --git a/src/dawn/tests/end2end/QueueTests.cpp b/src/dawn/tests/end2end/QueueTests.cpp
index e09c8f8..5c5476a 100644
--- a/src/dawn/tests/end2end/QueueTests.cpp
+++ b/src/dawn/tests/end2end/QueueTests.cpp
@@ -546,6 +546,9 @@
// Test writing to varying mips
TEST_P(QueueWriteTextureTests, TextureWriteToMip) {
+ // TODO(crbug.com/540087398): NPOT mipmapped depth/stencil textures disallowed due to driver bug
+ DAWN_SUPPRESS_TEST_IF(IsImgTec());
+
constexpr uint32_t kWidth = 259;
constexpr uint32_t kHeight = 127;
Original Bug Report
IMG: Heap OOB write in depth/stencil image layout computation
- Attack surface: WebGPU, reachable from an untrusted web page in the default Chrome for Android configuration.
- Impact: GPU-memory corruption in the unsandboxed Chrome GPU process.
Android internal bug: b/528131119