Chrome · WebGPU
CVE-2025-12725
OOB in WebGPU
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Psrc/dawn/tests/end2end/PolyfillBuiltinSimpleTests.cpp |
modified | |
switchsrc/dawn/tests/end2end/PolyfillBuiltinSimpleTests.cpp |
modified | |
ifsrc/dawn/tests/end2end/PolyfillBuiltinSimpleTests.cpp |
modified |
Files Changed
src/dawn/native/Toggles.cppsrc/dawn/native/Toggles.hsrc/dawn/native/vulkan/PhysicalDeviceVk.cppsrc/dawn/native/vulkan/ShaderModuleVk.cppsrc/dawn/tests/end2end/PolyfillBuiltinSimpleTests.cpp
Patch
From 81eda65489d8a1781ee4a076f8a3d2dceda03835 Mon Sep 17 00:00:00 2001 From: Peter McNeeley <[email protected]> Date: Fri, 03 Oct 2025 14:34:11 -0700 Subject: [PATCH] [tint] Polyfill case switch with if Bug: 443906252 Change-Id: Ibec456e950717af8b6a2e3ff192291703026f7d9 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/262994 Reviewed-by: dan sinclair <[email protected]> Reviewed-by: James Price <[email protected]> Commit-Queue: Peter McNeeley <[email protected]> --- diff --git a/src/dawn/native/Toggles.cpp b/src/dawn/native/Toggles.cpp index f7cc6c4..0b2a0e8 100644 --- a/src/dawn/native/Toggles.cpp +++ b/src/dawn/native/Toggles.cpp @@ -566,6 +566,10 @@ "and unpack4xU8() on D3D12 backends. Note that these functions are always polyfilled on all " "other backends right now.", "https://crbug.com/tint/1497", ToggleStage::Device}}, + {Toggle::VulkanPolyfillSwitchWithIf, + {"vulkan_polyfill_switch_with_if", + "Polyfill switch statements with if/else statements on Vulkan.", + "https://crbug.com/443906252", ToggleStage::Device}}, {Toggle::ExposeWGSLTestingFeatures, {"expose_wgsl_testing_features", "Make the Instance expose the ChromiumTesting* features for testing of " diff --git a/src/dawn/native/Toggles.h b/src/dawn/native/Toggles.h index c4c299a..090482b 100644 --- a/src/dawn/native/Toggles.h +++ b/src/dawn/native/Toggles.h @@ -138,6 +138,7 @@ PolyfillPackUnpack4x8Norm, EnableSubgroupsIntelGen9, D3D12PolyFillPackUnpack4x8, + VulkanPolyfillSwitchWithIf, ExposeWGSLTestingFeatures, ExposeWGSLExperimentalFeatures, DisablePolyfillsOnIntegerDivisonAndModulo, diff --git a/src/dawn/native/vulkan/PhysicalDeviceVk.cpp b/src/dawn/native/vulkan/PhysicalDeviceVk.cpp index 8467b71..3ac8785 100644 --- a/src/dawn/native/vulkan/PhysicalDeviceVk.cpp +++ b/src/dawn/native/vulkan/PhysicalDeviceVk.cpp @@ -979,6 +979,9 @@ // TODO(crbug.com/437807243): If newer driver version without bug is released then we can // gate this on driver version. deviceToggles->Default(Toggle::VulkanIncompletePipelineCacheWorkaround, true); + + // crbug.com/443906252: Polyfill for case switch with large ranges. + deviceToggles->Default(Toggle::VulkanPolyfillSwitchWithIf, true); } if (IsAndroidARM()) { diff --git a/src/dawn/native/vulkan/ShaderModuleVk.cpp b/src/dawn/native/vulkan/ShaderModuleVk.cpp index 08c8e5e..468a630 100644 --- a/src/dawn/native/vulkan/ShaderModuleVk.cpp +++ b/src/dawn/native/vulkan/ShaderModuleVk.cpp @@ -293,6 +293,8 @@ GetDevice()->IsToggleEnabled(Toggle::PolyFillPacked4x8DotProduct); req.tintOptions.polyfill_pack_unpack_4x8_norm = GetDevice()->IsToggleEnabled(Toggle::PolyfillPackUnpack4x8Norm); + req.tintOptions.polyfill_case_switch = + GetDevice()->IsToggleEnabled(Toggle::VulkanPolyfillSwitchWithIf); req.tintOptions.polyfill_subgroup_broadcast_f16 = GetDevice()->IsToggleEnabled(Toggle::EnableSubgroupsIntelGen9); req.tintOptions.disable_polyfill_integer_div_mod = diff --git a/src/dawn/tests/end2end/PolyfillBuiltinSimpleTests.cpp b/src/dawn/tests/end2end/PolyfillBuiltinSimpleTests.cpp index 53ca6ac..97a7b3d 100644 --- a/src/dawn/tests/end2end/PolyfillBuiltinSimpleTests.cpp +++ b/src/dawn/tests/end2end/PolyfillBuiltinSimpleTests.cpp @@ -229,6 +229,142 @@ EXPECT_BUFFER_U32_RANGE_EQ(expected.data(), output, 0, expected.size()); } +TEST_P(PolyfillBuiltinSimpleTests, CaseSwitchToIf) { + std::string kShaderCode = R"( + struct Data { values: array<i32> }; + @group(0) @binding(0) var<storage, read> input_data: Data; + @group(0) @binding(1) var<storage, read_write> output_data: Data; + + @compute @workgroup_size(4) + fn main(@builtin(global_invocation_id) global_id: vec3<u32>) { + var input_ = input_data.values[global_id.x]; + var ret = 0i; + switch( input_ ) { + case 1: { + ret = 3; + } + case 2:{ + ret = 7; + } + case -2147483648:{ + ret = 71; + } + case 123, 87:{ + ret = 11; + } + case -1:{ + ret = 33; + } + default { + ret = 82; + } + } + output_data.values[global_id.x] = ret; + } + )"; + + wgpu::ComputePipeline pipeline = CreateComputePipeline(kShaderCode); + uint32_t kDefaultVal = 0; + std::vector<uint32_t> init_input = {uint32_t(std::numeric_limits<int32_t>::lowest()), + uint32_t(-15), 17, 123}; + + wgpu::Buffer input = CreateBuffer(init_input); + wgpu::Buffer output = CreateBuffer(4, kDefaultVal); + wgpu::BindGroup bindGroup = + utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), {{0, input}, {1, output}}); + + wgpu::CommandBuffer commands; + { + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); + pass.SetPipeline(pipeline); + pass.SetBindGroup(0, bindGroup); + pass.DispatchWorkgroups(64); + pass.End(); + commands = encoder.Finish(); + } + + queue.Submit(1, &commands); + std::vector<uint32_t> expected = {71, 82, 82, 11}; + + EXPECT_BUFFER_U32_RANGE_EQ(expected.data(), output, 0, expected.size()); +} + +TEST_P(PolyfillBuiltinSimpleTests, CaseSwitchToIfComplex) { + std::string kShaderCode = R"( + @group(0) @binding(0) var<storage, read> input_data: array<i32>; + @group(0) @binding(1) var<storage, read_write> output_data: array<i32>; + + @compute @workgroup_size(4) + fn main(@builtin(global_invocation_id) global_id: vec3<u32>) { + var input_ = input_data[global_id.x]; + var ret = 0i; + switch( input_ ) { + case 1: { + ret = 3; + } + case -2:{ + switch(input_){ + case 1: { + ret = 3; + } + case -2:{ + ret = 4; + } + default{ + ret = 99; + } + } + break; + ret = 7; + } + case -2147483648:{ + if(input_ == 17){ + ret = 71; + break; + } + ret = 13; + } + case 3, 5:{ + if(input_ == 3){ + break; + } + ret = 11; + } + default { + ret = 82; + } + } + output_data[global_id.x] = ret; + } + )"; + + wgpu::ComputePipeline pipeline = CreateComputePipeline(kShaderCode); + uint32_t kDefaultVal = 0; + std::vector<uint32_t> init_input = {uint32_t(std::numeric_limits<int32_t>::lowest()), + uint32_t(-2), 3, 5}; + std::vector<uint32_t> expected = {13, 4, 0, 11}; + wgpu::Buffer input = CreateBuffer(init_input); + wgpu::Buffer output = CreateBuffer(4, kDefaultVal); + wgpu::BindGroup bindGroup = + utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), {{0, input}, {1, output}}); + + wgpu::CommandBuffer commands; + { + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); + pass.SetPipeline(pipeline);
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/src/dawn/tests/end2end/PolyfillBuiltinSimpleTests.cpp b/src/dawn/tests/end2end/PolyfillBuiltinSimpleTests.cpp
index 53ca6ac..97a7b3d 100644
--- a/src/dawn/tests/end2end/PolyfillBuiltinSimpleTests.cpp
+++ b/src/dawn/tests/end2end/PolyfillBuiltinSimpleTests.cpp
@@ -229,6 +229,142 @@
EXPECT_BUFFER_U32_RANGE_EQ(expected.data(), output, 0, expected.size());
}
+TEST_P(PolyfillBuiltinSimpleTests, CaseSwitchToIf) {
+ std::string kShaderCode = R"(
+ struct Data { values: array<i32> };
+ @group(0) @binding(0) var<storage, read> input_data: Data;
+ @group(0) @binding(1) var<storage, read_write> output_data: Data;
+
+ @compute @workgroup_size(4)
+ fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
+ var input_ = input_data.values[global_id.x];
+ var ret = 0i;
+ switch( input_ ) {
+ case 1: {
+ ret = 3;
+ }
+ case 2:{
+ ret = 7;
+ }
+ case -2147483648:{
+ ret = 71;
+ }
+ case 123, 87:{
+ ret = 11;
+ }
+ case -1:{
+ ret = 33;
+ }
+ default {
+ ret = 82;
+ }
+ }
+ output_data.values[global_id.x] = ret;
+ }
+ )";
+
+ wgpu::ComputePipeline pipeline = CreateComputePipeline(kShaderCode);
+ uint32_t kDefaultVal = 0;
+ std::vector<uint32_t> init_input = {uint32_t(std::numeric_limits<int32_t>::lowest()),
+ uint32_t(-15), 17, 123};
+
+ wgpu::Buffer input = CreateBuffer(init_input);
+ wgpu::Buffer output = CreateBuffer(4, kDefaultVal);
+ wgpu::BindGroup bindGroup =
+ utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), {{0, input}, {1, output}});
+
+ wgpu::CommandBuffer commands;
+ {
+ wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+ wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
+ pass.SetPipeline(pipeline);
+ pass.SetBindGroup(0, bindGroup);
+ pass.DispatchWorkgroups(64);
+ pass.End();
+ commands = encoder.Finish();
+ }
+
+ queue.Submit(1, &commands);
+ std::vector<uint32_t> expected = {71, 82, 82, 11};
+
+ EXPECT_BUFFER_U32_RANGE_EQ(expected.data(), output, 0, expected.size());
+}
+
+TEST_P(PolyfillBuiltinSimpleTests, CaseSwitchToIfComplex) {
+ std::string kShaderCode = R"(
+ @group(0) @binding(0) var<storage, read> input_data: array<i32>;
+ @group(0) @binding(1) var<storage, read_write> output_data: array<i32>;
+
+ @compute @workgroup_size(4)
+ fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
+ var input_ = input_data[global_id.x];
+ var ret = 0i;
+ switch( input_ ) {
+ case 1: {
+ ret = 3;
+ }
+ case -2:{
+ switch(input_){
+ case 1: {
+ ret = 3;
+ }
+ case -2:{
+ ret = 4;
+ }
+ default{
+ ret = 99;
+ }
+ }
+ break;
+ ret = 7;
+ }
+ case -2147483648:{
+ if(input_ == 17){
+ ret = 71;
+ break;
+ }
+ ret = 13;
+ }
+ case 3, 5:{
+ if(input_ == 3){
+ break;
+ }
+ ret = 11;
+ }
+ default {
+ ret = 82;
+ }
+ }
+ output_data[global_id.x] = ret;
+ }
+ )";
+
+ wgpu::ComputePipeline pipeline = CreateComputePipeline(kShaderCode);
+ uint32_t kDefaultVal = 0;
+ std::vector<uint32_t> init_input = {uint32_t(std::numeric_limits<int32_t>::lowest()),
+ uint32_t(-2), 3, 5};
+ std::vector<uint32_t> expected = {13, 4, 0, 11};
+ wgpu::Buffer input = CreateBuffer(init_input);
+ wgpu::Buffer output = CreateBuffer(4, kDefaultVal);
+ wgpu::BindGroup bindGroup =
+ utils::MakeBindGroup(device, pipeline.GetBindGroupLayout(0), {{0, input}, {1, output}});
+
+ wgpu::CommandBuffer commands;
+ {
+ wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+ wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
+ pass.SetPipeline(pipeline);
+ pass.SetBindGroup(0, bindGroup);
+ pass.DispatchWorkgroups(64);
+ pass.End();
+ commands = encoder.Finish();
+ }
+
+ queue.Submit(1, &commands);
+
+ EXPECT_BUFFER_U32_RANGE_EQ(expected.data(), output, 0, expected.size());
+}
+
DAWN_INSTANTIATE_TEST(PolyfillBuiltinSimpleTests,
D3D12Backend(),
D3D11Backend(),
@@ -237,6 +373,7 @@
D3D12Backend({"scalarize_max_min_clamp"}),
MetalBackend({"scalarize_max_min_clamp"}),
VulkanBackend({"scalarize_max_min_clamp"}),
+ VulkanBackend({"vulkan_polyfill_switch_with_if"}),
D3D11Backend({"scalarize_max_min_clamp"}),
OpenGLESBackend());
diff --git a/src/tint/lang/spirv/writer/raise/case_switch_to_if_else_test.cc b/src/tint/lang/spirv/writer/raise/case_switch_to_if_else_test.cc
new file mode 100644
index 0000000..738d69f
--- /dev/null
+++ b/src/tint/lang/spirv/writer/raise/case_switch_to_if_else_test.cc
@@ -0,0 +1,1375 @@
+// Copyright 2025 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/spirv/writer/raise/case_switch_to_if_else.h"
+
+#include <utility>
+
+#include "src/tint/lang/core/ir/transform/helper_test.h"
+
+namespace tint::spirv::writer::raise {
+namespace {
+
+using namespace tint::core::fluent_types; // NOLINT
+using namespace tint::core::number_suffixes; // NOLINT
+
+using SpirvWriter_CaseSwitchToIfElseTest = core::ir::transform::TransformTest;
+
+TEST_F(SpirvWriter_CaseSwitchToIfElseTest, BasicSwitch) {
+ auto* cond = b.FunctionParam("param0", ty.i32());
+ auto* func = b.Function("foo", ty.void_());
+ func->SetParams({cond});
+ b.Append(func->Block(), [&] {
+ auto* s = b.Switch(cond);
+ b.Append(b.Case(s, {b.Constant(-1_i)}), [&] { //
+ b.Return(func);
+ });
+ b.Append(b.Case(s, {b.Constant(2_i)}), [&] { //
+ b.Return(func);
+ });
+ b.Append(b.DefaultCase(s), [&] { //
+ b.Return(func);
+ });
+ b.Unreachable();
+ });
+
+ auto* src = R"(
+%foo = func(%param0:i32):void {
+ $B1: {
+ switch %param0 [c: (-1i, $B2), c: (2i, $B3), c: (default, $B4)] { # switch_1
+ $B2: { # case
+ ret
+ }
+ $B3: { # case
+ ret
+ }
+ $B4: { # case
+ ret
+ }
+ }
+ unreachable
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%foo = func(%param0:i32):void {
+ $B1: {
+ switch %param0 [c: (default, $B2)] { # switch_1
+ $B2: { # case
+ %3:bool = eq %param0, -1i
+ if %3 [t: $B3] { # if_1
+ $B3: { # true
+ ret
+ }
+ }
+ %4:bool = eq %param0, 2i
+ if %4 [t: $B4] { # if_2
+ $B4: { # true
+ ret
+ }
+ }
+ if true [t: $B5] { # if_3
+ $B5: { # true
+ ret
+ }
+ }
+ unreachable
+ }
+ }
+ unreachable
+ }
+}
+)";
+
+ Run(CaseSwitchToIfElse);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(SpirvWriter_CaseSwitchToIfElseTest, ReorderedBasicSwitch) {
+ auto* cond = b.FunctionParam("param0", ty.i32());
+ auto* func = b.Function("foo", ty.void_());
+ func->SetParams({cond});
+ b.Append(func->Block(), [&] {
+ auto* s = b.Switch(cond);
+ b.Append(b.DefaultCase(s), [&] { //
+ b.Return(func);
+ });
+ b.Append(b.Case(s, {b.Constant(2_i)}), [&] { //
+ b.Return(func);
+ });
+
+ b.Append(b.Case(s, {b.Constant(-1_i)}), [&] { //
+ b.Return(func);
+ });
+
+ b.Unreachable();
+ });
+
+ auto* src = R"(
+%foo = func(%param0:i32):void {
+ $B1: {
+ switch %param0 [c: (default, $B2), c: (2i, $B3), c: (-1i, $B4)] { # switch_1
+ $B2: { # case
+ ret
... (truncated)
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page