CVE-2026-14421
Overview
Files Changed
src/dawn/native/vulkan/PhysicalDeviceVk.cppsrc/dawn/native/vulkan/PhysicalDeviceVk.h
Patch
From 2d938c77b65285d2828a370a91bc92e15b9728b8 Mon Sep 17 00:00:00 2001
From: Corentin Wallez <cwallez@chromium.org>
Date: Thu, 28 May 2026 11:36:15 -0700
Subject: [PATCH] [dawn][vk] Use VkDriverId to decide workaround toggles.
The IsAndroidFoo conditions matched GPUs only on Android when the same
drivers may be used on ChromeOS. To broaden the checks without
penalizing other drivers, PhysicalDeviceVk now uses VkDriverId that lets
us detect which driver we're running on. Assuming all the proprietary
drivers are the same source (and that the workarounds are for driver
issues, not hardware issues), this gives more precise conditions for
when the workaround is needed.
Also group toggles under IsIntelMesa together.
Fixed: 517033235
Change-Id: I67775c8a508f03e6adaf440d6577a319c1f65a78
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/311895
Reviewed-by: Brandon Jones <bajones@chromium.org>
Commit-Queue: Corentin Wallez <cwallez@chromium.org>
---
diff --git a/src/dawn/native/vulkan/PhysicalDeviceVk.cpp b/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
index 179d2d5..7c6eee9 100644
--- a/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
+++ b/src/dawn/native/vulkan/PhysicalDeviceVk.cpp
@@ -1002,7 +1002,7 @@
// Vulkan SPEC and drivers.
deviceToggles->Default(Toggle::UseTemporaryBufferInCompressedTextureToTextureCopy, true);
- if (IsAndroidQualcomm()) {
+ if (MayBeQualcommProprietary()) {
// dawn:1564, dawn:1897: Recording a compute pass after a render pass in the same command
// buffer frequently causes a crash on Qualcomm GPUs. To work around that bug, split the
// command buffer any time we are about to record a compute pass when a render pass has
@@ -1036,11 +1036,6 @@
deviceToggles->Default(Toggle::VulkanSampleCompare2DWorkaround, true);
}
- if (IsIntelMesa()) {
- // chromium:448873316: Non-scalar (vector) saturate from uniform fails.
- deviceToggles->Default(Toggle::SaturateAsMinMaxF16, true);
- }
-
if (IsPixel10() || IsAndroidSamsung()) {
// Pixel 10 has a bug in vkGetPipelineCacheData(), see https://crbug.com/437807243.
// Samsung Xclipse GPUs appear to have the same problem, see https://crbug.com/487613497.
@@ -1049,7 +1044,7 @@
deviceToggles->Default(Toggle::VulkanIncompletePipelineCacheWorkaround, true);
}
- if (gpu_info::IsImgTec(GetVendorId())) {
+ if (MayBeImaginationProprietary()) {
// crbug.com/443906252: Polyfill for case switch with large ranges.
deviceToggles->Default(Toggle::VulkanPolyfillSwitchWithIf, true);
}
@@ -1066,7 +1061,7 @@
}
}
- if (IsAndroidARM()) {
+ if (MayBeArmProprietary()) {
// dawn:1550: Resolving multiple color targets in a single pass fails on ARM GPUs. To
// work around the issue, passes that resolve to multiple color targets will instead be
// forced to store the multisampled targets and do the resolves as separate passes injected
@@ -1077,9 +1072,7 @@
// `unpack4x8unorm` methods can have issues on ARM. To work around the issue we re-write the
// pack/unpack calls and do the packing manually.
deviceToggles->Default(Toggle::PolyfillPackUnpack4x8Norm, true);
- }
- if (gpu_info::IsARM(GetVendorId())) {
// chromium:387000529: Arm devices have issues passing texture handles as parameters to
// functions for accesses without a sampler (TextureLoad).
deviceToggles->Default(Toggle::VulkanDirectVariableAccessTransformHandle, true);
@@ -1106,40 +1099,45 @@
}
if (IsIntelMesa()) {
+ // chromium:448873316: Non-scalar (vector) saturate from uniform fails.
+ deviceToggles->Default(Toggle::SaturateAsMinMaxF16, true);
+
// Polyfill a clamp of `id` param in subgroupShuffle to follow spec limitations.
// See crbug.com/435246627
deviceToggles->Default(Toggle::SubgroupShuffleClamped, true);
- }
- if (IsIntelMesa() && gpu_info::IsIntelGen12LP(GetVendorId(), GetDeviceId())) {
- // dawn:1688: Intel Mesa driver has a bug about reusing the VkDeviceMemory that was
- // previously bound to a 2D VkImage. To work around that bug we have to disable the resource
- // sub-allocation for 2D textures with CopyDst or RenderAttachment usage.
- const gpu_info::DriverVersion kBuggyDriverVersion = {21, 3, 6, 0};
- if (GetDriverVersion() >= kBuggyDriverVersion) {
- deviceToggles->Default(
- Toggle::DisableSubAllocationFor2DTextureWithCopyDstOrRenderAttachment, true);
+ if (gpu_info::IsIntelGen12LP(GetVendorId(), GetDeviceId())) {
+ // dawn:1688: Intel Mesa driver has a bug about reusing the VkDeviceMemory that was
+ // previously bound to a 2D VkImage. To work around that bug we have to disable the
+ // resource sub-allocation for 2D textures with CopyDst or RenderAttachment usage.
+ const gpu_info::DriverVersion kBuggyDriverVersion = {21, 3, 6, 0};
+ if (GetDriverVersion() >= kBuggyDriverVersion) {
+ deviceToggles->Default(
+ Toggle::DisableSubAllocationFor2DTextureWithCopyDstOrRenderAttachment, true);
+ }
+
+ // chromium:1361662: Mesa driver has a bug clearing R8 mip-leveled textures on Intel
+ // Gen12 GPUs. Work around it by clearing the whole texture as soon as they are created.
+ const gpu_info::DriverVersion kFixedDriverVersion = {23, 1, 0, 0};
+ if (GetDriverVersion() < kFixedDriverVersion) {
+ deviceToggles->Default(Toggle::VulkanClearGen12TextureWithCCSAmbiguateOnCreation,
+ true);
+ }
}
- // chromium:1361662: Mesa driver has a bug clearing R8 mip-leveled textures on Intel Gen12
- // GPUs. Work around it by clearing the whole texture as soon as they are created.
- const gpu_info::DriverVersion kFixedDriverVersion = {23, 1, 0, 0};
- if (GetDriverVersion() < kFixedDriverVersion) {
- deviceToggles->Default(Toggle::VulkanClearGen12TextureWithCCSAmbiguateOnCreation, true);
- }
- }
-
- if (IsIntelMesa() && (gpu_info::IsIntelGen12LP(GetVendorId(), GetDeviceId()) ||
- gpu_info::IsIntelGen12HP(GetVendorId(), GetDeviceId()))) {
- // Intel Mesa driver has a bug where vkCmdCopyQueryPoolResults fails to write overlapping
- // queries to a same buffer after the buffer is accessed by a compute shader with correct
- // resource barriers, which may caused by flush and memory coherency issue on Intel Gen12
- // GPUs. Workaround for it to clear the buffer before vkCmdCopyQueryPoolResults on Mesa
- // driver version < 23.1.3.
- const gpu_info::DriverVersion kBuggyDriverVersion = {21, 2, 0, 0};
- const gpu_info::DriverVersion kFixedDriverVersion = {23, 1, 3, 0};
- if (GetDriverVersion() >= kBuggyDriverVersion && GetDriverVersion() < kFixedDriverVersion) {
- deviceToggles->Default(Toggle::ClearBufferBeforeResolveQueries, true);
+ if (gpu_info::IsIntelGen12LP(GetVendorId(), GetDeviceId()) ||
+ gpu_info::IsIntelGen12HP(GetVendorId(), GetDeviceId())) {
+ // Intel Mesa driver has a bug where vkCmdCopyQueryPoolResults fails to write
+ // overlapping queries to a same buffer after the buffer is accessed by a compute shader
+ // with correct resource barriers, which may caused by flush and memory coherency issue
+ // on Intel Gen12 GPUs. Workaround for it to clear the buffer before
+ // vkCmdCopyQueryPoolResults on Mesa driver version < 23.1.3.
+ const gpu_info::DriverVersion kBuggyDriverVersion = {21, 2, 0, 0};
+ const gpu_info::DriverVersion kFixedDriverVersion = {23, 1, 3, 0};
+ if (GetDriverVersion() >= kBuggyDriverVersion &&
+ GetDriverVersion() < kFixedDriverVersion) {
+ deviceToggles->Default(Toggle::ClearBufferBeforeResolveQueries, true);
+ }
}
}
@@ -1413,6 +1411,33 @@
return gpu_info::IsGoogleSwiftshader(GetVendorId(), GetDeviceId());
}
+bool PhysicalDevice::MayBeArmProprietary() const {
+ if (!gpu_info::IsARM(GetVendorId())) {
+ return false;
+ }
+
+ return !mDeviceInfo.HasExt(DeviceExt::DriverProperties) ||
+ mDeviceInfo.driverProperties.driverID == VK_DRIVER_ID_ARM_PROPRIETARY;
+}
+
+bool PhysicalDevice::MayBeQualcommProprietary() const {
+ if (!gpu_info::IsQualcommPCI(GetVendorId())) {
+ return false;
+ }
+
+ return !mDeviceInfo.HasExt(DeviceExt::DriverProperties) ||
+ mDeviceInfo.driverProperties.driverID == VK_DRIVER_ID_QUALCOMM_PROPRIETARY;
+}
+
+bool PhysicalDevice::MayBeImaginationProprietary() const {
+ if (!gpu_info::IsImgTec(GetVendorId())) {
+ return false;
+ }
+
+ return !mDeviceInfo.HasExt(DeviceExt::DriverProperties) ||
+ mDeviceInfo.driverProperties.driverID == VK_DRIVER_ID_IMAGINATION_PROPRIETARY;
+}
+
std::optional<uint32_t> PhysicalDevice::FindDefaultComputeSubgroupSize() const {
if (!mDeviceInfo.HasExt(DeviceExt::SubgroupSizeControl)) {
return std::nullopt;
diff --git a/src/dawn/native/vulkan/PhysicalDeviceVk.h b/src/dawn/native/vulkan/PhysicalDeviceVk.h
index 14f8e18..9f305e8 100644
--- a/src/dawn/native/vulkan/PhysicalDeviceVk.h
+++ b/src/dawn/native/vulkan/PhysicalDeviceVk.h
@@ -65,11 +65,18 @@
bool IsAndroidARM() const;
bool IsAndroidSamsung() const;
bool IsAndroidImgTec() const;
+ bool IsAndroidHuawei() const;
bool IsPixel10() const;
+ bool IsSwiftshader() const;
+
+ // Check using VkDriverId, which is available in Vk 1.2 or an extension.
bool IsIntelMesa() const;
bool IsAmdMesa() const;
- bool IsAndroidHuawei() const;
- bool IsSwiftshader() const;
Original Bug Report
Potential GPU heap leak on ChromeOS-ARM due to disabled Mali multi-resolve workaround
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 potential WebGPU lazy-clear bypass on ChromeOS-ARM devices allows sandboxed pages to leak cross-origin GPU heap memory. The multi-resolve workaround for ARM Mali GPUs is gated by IsAndroidARM(), which is disabled on ChromeOS. Consequently, render passes with multiple resolve targets can fail silently, bypassing initialization tracking and exposing uninitialized memory.
Affected files:
third_party/dawn/src/dawn/native/vulkan/PhysicalDeviceVk.cppthird_party/dawn/src/dawn/native/CommandBuffer.cpp
Estimated timestamp from git blame: 2023-07-28
Root Cause Analysis
In third_party/dawn/src/dawn/native/vulkan/PhysicalDeviceVk.cpp, the ResolveMultipleAttachmentInSeparatePasses workaround (introduced to address dawn:1550) is only enabled if the compile-time predicate IsAndroidARM() returns true:
if (IsAndroidARM()) {
// dawn:1550: Resolving multiple color targets in a single pass fails on ARM GPUs.
deviceToggles->Default(Toggle::ResolveMultipleAttachmentInSeparatePasses, true);
// dawn:379551588: pack4x8* methods can have issues on ARM.
deviceToggles->Default(Toggle::PolyfillPackUnpack4x8Norm, true);
}
However, PhysicalDevice::IsAndroidARM() is gated by a compile-time check using the #if DAWN_PLATFORM_IS(ANDROID) macro:
bool PhysicalDevice::IsAndroidARM() const {
#if DAWN_PLATFORM_IS(ANDROID)
return gpu_info::IsARM(GetVendorId());
#else
return false;
#endif
}
On ChromeOS-ARM devices (such as Chromebooks powered by ARM-based MediaTek Kompanio chipsets running the proprietary ARM Mali Vulkan driver), this predicate evaluates to false and the workaround toggle remains disabled. Consequently, Dawn records a single VkRenderPass with multiple resolve attachments directly on a driver that is potentially vulnerable to the dawn:1550 multi-resolve bug.
Potential Vulnerability Mechanism (Lazy-Clear Bypass)
WebGPU textures must be zero-initialized prior to being read back to prevent information disclosure. This is guaranteed by Dawn’s LazyClearResourceOnFirstUse toggle.
During render pass encoding, resolve targets are not cleared; they are assumed to be overwritten by the resolve operation. Thus, LazyClearRenderPassAttachments in third_party/dawn/src/dawn/native/CommandBuffer.cpp flags these resolve targets as initialized before execution:
if (hasResolveTarget) {
TextureViewBase* resolveView = attachmentInfo.resolveTarget.Get();
...
if (!resolveView->GetTexture()->IsSubresourceContentInitialized(
resolveView->GetSubresourceRange())) {
...
// We need to set the resolve target to initialized so that it does not get
// cleared later in the pipeline.
resolveView->GetTexture()->SetIsSubresourceContentInitialized(
true, resolveView->GetSubresourceRange());
}
}
On ChromeOS-ARM, since the multi-resolve workaround is disabled, a multi-resolve render pass is recorded. Due to the driver bug, the GPU potentially silently fails to write to one or more of the resolve targets. However, because Dawn has already marked these subresources as initialized, subsequent read actions (e.g., copyTextureToBuffer followed by mapping) skip the EnsureSubresourceContentInitialized safety check.
This potentially allows an attacker-controlled page to read back stale, uninitialized GPU-heap memory containing recycled allocations from other origins (such as Skia compositor tiles, WebGL/WebGPU textures, or decoded video frames).
Potential Trigger Steps
Note: The following are suggested/potential steps to reproduce. Our tooling agent doesn’t yet have the ability to run code, and we do not have a working proof of concept that has been successfully run.
- On a ChromeOS-ARM Mali device where WebGPU is enabled, navigate to a page that requests a WebGPU device.
- Allocate a multisampled (MSAA) color texture along with two freshly-created resolve target textures (never used, meaning they are uninitialized).
- Encode and submit a single render pass containing multiple resolve attachments.
LazyClearRenderPassAttachmentsflags both resolve targets as initialized.- The underlying ARM Mali driver silently fails to execute the resolve for at least one target, leaving its recycled heap memory untouched.
- Read back the unwritten resolve target via
copyTextureToBufferand map the buffer to retrieve raw cross-origin GPU memory.
Suggested Fix
Replace the platform-dependent IsAndroidARM() check in PhysicalDeviceVk.cpp with a platform-neutral vendor check using gpu_info::IsARM(GetVendorId()) (similar to how other ARM Mali workarounds in the same file are gated):
if (gpu_info::IsARM(GetVendorId())) {
// dawn:1550: Resolving multiple color targets in a single pass fails on ARM GPUs.
deviceToggles->Default(Toggle::ResolveMultipleAttachmentInSeparatePasses, true);
// dawn:379551588: pack4x8* methods can have issues on ARM.
deviceToggles->Default(Toggle::PolyfillPackUnpack4x8Norm, true);
}
Evaluated with Chrome root at commit: b1520ef4a76878853a31f0943b565e42060edec8
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.