Chrome · Tint
CVE-2026-87592
OOB in Tint
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifsrc/tint/lang/spirv/writer/raise/builtin_polyfill.cc |
modified | |
forsrc/tint/lang/spirv/writer/raise/builtin_polyfill.cc |
modified |
Files Changed
src/dawn/native/Toggles.cppsrc/dawn/native/Toggles.hsrc/dawn/native/vulkan/PhysicalDeviceVk.cppsrc/dawn/native/vulkan/ShaderModuleVk.cppsrc/tint/lang/spirv/writer/common/options.hsrc/tint/lang/spirv/writer/raise/builtin_polyfill.cc
Patch
From c50a11059c92ab26a349732afcf5e0fb2afda438 Mon Sep 17 00:00:00 2001 From: Natalie Chouinard <[email protected]> Date: Wed, 03 Jun 2026 19:30:48 -0700 Subject: [PATCH] [tint][spirv] Clamp subgroupShuffle to actual size Replace the hardcoded max subgroup size mask in the SPIR-V subgroupShuffle polyfill with the actual subgroup size from the builtin value. Because SubgroupSize is guaranteed to be a power-of-2 in Vulkan, it is still safe to subtract 1 and mask to clamp. This polyfill is also now enabled for all SPIR-V generation, rather than limited by a toggle, since the requirement of invocation ID to be less than the subgroup size is a general SPIR-V spec requirement to avoid poison values, not specific to a driver issue. https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#OpGroupNonUniformShuffle Fixes: 513947572 Change-Id: Id6e68170e467284152212e8c014d2c9688760175 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/313515 Commit-Queue: Natalie Chouinard <[email protected]> SLSA-Policy-Verified: SLSA Policy Verification Service <[email protected]> Reviewed-by: James Price <[email protected]> --- diff --git a/src/dawn/native/Toggles.cpp b/src/dawn/native/Toggles.cpp index 58df017..a18adb6 100644 --- a/src/dawn/native/Toggles.cpp +++ b/src/dawn/native/Toggles.cpp @@ -622,10 +622,6 @@ {Toggle::MetalPolyfillClampFloat, {"metal_polyfill_clamp_float", "Polyfill clamp function for floating point (metal).", "https://crbug.com/407109056", ToggleStage::Device}}, - {Toggle::SubgroupShuffleClamped, - {"subgroup_shuffle_clamped", - "Polyfill subgroupShuffle by clamping the id param to within maximum possible subgroup size.", - "https://crbug.com/dawn/2502", ToggleStage::Device}}, {Toggle::VulkanSampleCompareDepthCubeArrayWorkaround, {"vulkan_sample_compare_depth_cube_array_workaround", "Polyfill specific case of textureSampleCompare(Level) with workaround.", diff --git a/src/dawn/native/Toggles.h b/src/dawn/native/Toggles.h index 38a8a90..02fde65 100644 --- a/src/dawn/native/Toggles.h +++ b/src/dawn/native/Toggles.h @@ -152,7 +152,6 @@ ScalarizeMaxMinClamp, SaturateAsMinMaxF16, MetalPolyfillClampFloat, - SubgroupShuffleClamped, VulkanSampleCompareDepthCubeArrayWorkaround, VulkanSampleCompare2DWorkaround, MetalDisableModuleConstantF16, diff --git a/src/dawn/native/vulkan/PhysicalDeviceVk.cpp b/src/dawn/native/vulkan/PhysicalDeviceVk.cpp index 2176cd0..82a60c5 100644 --- a/src/dawn/native/vulkan/PhysicalDeviceVk.cpp +++ b/src/dawn/native/vulkan/PhysicalDeviceVk.cpp @@ -1109,10 +1109,6 @@ // 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 (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 diff --git a/src/dawn/native/vulkan/ShaderModuleVk.cpp b/src/dawn/native/vulkan/ShaderModuleVk.cpp index 74fe3d7..a3c77c7 100644 --- a/src/dawn/native/vulkan/ShaderModuleVk.cpp +++ b/src/dawn/native/vulkan/ShaderModuleVk.cpp @@ -317,8 +317,6 @@ req.tintOptions.extensions.use_uniform_buffers = !GetDevice()->IsToggleEnabled(Toggle::DecomposeUniformBuffers); - req.tintOptions.workarounds.subgroup_shuffle_clamped = - GetDevice()->IsToggleEnabled(Toggle::SubgroupShuffleClamped); req.tintOptions.workarounds.texture_sample_compare_depth_cube_array = GetDevice()->IsToggleEnabled(Toggle::VulkanSampleCompareDepthCubeArrayWorkaround); req.tintOptions.workarounds.texture_sample_compare_2d_polyfill = diff --git a/src/tint/lang/spirv/writer/common/options.h b/src/tint/lang/spirv/writer/common/options.h index 8c4d22d..c9a5dd9 100644 --- a/src/tint/lang/spirv/writer/common/options.h +++ b/src/tint/lang/spirv/writer/common/options.h @@ -87,10 +87,6 @@ /// and `unpack4x8unorm` builtins bool polyfill_pack_unpack_4x8_norm = false; - /// Set to `true` to generate a polyfill clamp of `id` param of subgroupShuffle to within - /// the spec max subgroup size. - bool subgroup_shuffle_clamped = false; - /// Set to 'true' to force workaround for 'textureSampleCompare(Level)' for texture arrays /// of cube depth. bool texture_sample_compare_depth_cube_array = false; @@ -132,7 +128,6 @@ scalarize_max_min_clamp, dva_transform_handle, polyfill_pack_unpack_4x8_norm, - subgroup_shuffle_clamped, texture_sample_compare_depth_cube_array, texture_sample_compare_2d_polyfill, polyfill_subgroup_broadcast_f16, diff --git a/src/tint/lang/spirv/writer/raise/builtin_polyfill.cc b/src/tint/lang/spirv/writer/raise/builtin_polyfill.cc index 326b351..7d6c803 100644 --- a/src/tint/lang/spirv/writer/raise/builtin_polyfill.cc +++ b/src/tint/lang/spirv/writer/raise/builtin_polyfill.cc @@ -184,6 +184,9 @@ /// The type manager. core::type::Manager& ty{ir.Types()}; + /// Module-scoped variable for subgroup size mask. + core::ir::Var* subgroup_size_mask_ = nullptr; + /// Process the module. void Process() { // Find the builtins that need replacing. @@ -249,9 +252,7 @@ case core::BuiltinFn::kSubgroupShuffleDown: case core::BuiltinFn::kSubgroupShuffleUp: case core::BuiltinFn::kSubgroupShuffleXor: { - bool clamped = config.subgroup_shuffle_clamped; - worklist.push_back( - [this, builtin, clamped] { SubgroupShuffle(builtin, clamped); }); + worklist.push_back([this, builtin] { SubgroupShuffle(builtin); }); break; } case core::BuiltinFn::kTextureDimensions: @@ -364,6 +365,48 @@ }); construct->SetArg(0, value); } + + if (subgroup_size_mask_) { + for (auto func : ir.functions) { + if (func->IsEntryPoint()) { + SetSubgroupSizeMaskForEntryPoint(func); + } + } + } + } + + /// Set the subgroup_size_mask variable from an entry point. + void SetSubgroupSizeMaskForEntryPoint(core::ir::Function* ep) { + b.InsertBefore(ep->Block()->Front(), [&] { + core::ir::Value* subgroup_size = nullptr; + for (auto* param : ep->Params()) { + if (param->Attributes().builtin == core::BuiltinValue::kSubgroupSize) { + subgroup_size = param; + break; + } + if (auto* str = param->Type()->As<core::type::Struct>()) { + for (auto* member : str->Members()) { + if (member->Attributes().builtin == core::BuiltinValue::kSubgroupSize) { + subgroup_size = + b.Access(ty.u32(), param, u32(member->Index()))->Result(); + break; + } + } + if (subgroup_size) { + break; + } + } + } + if (!subgroup_size) { + auto* param = b.FunctionParam("tint_subgroup_size", ty.u32()); + param->SetBuiltin(core::BuiltinValue::kSubgroupSize); + ep->AppendParam(param); + subgroup_size = param; + } + + auto* mask = b.Subtract(subgroup_size, 1_u); + b.Store(subgroup_size_mask_, mask); + }); } /// Create a literal operand. @@ -1248,10 +1291,10 @@ /// Handles SubgroupShuffle(), SubgroupShuffleDown(), SubgroupShuffleUp(), SubgroupShuffleXor() /// builtins. /// @param builtin the builtin call instruction - void SubgroupShuffle(core::ir::CoreBuiltinCall* builtin, bool clamp_subgroup_shuffle) { + void SubgroupShuffle(core::ir::CoreBuiltinCall* builtin) { TINT_IR_ASSERT(ir, builtin->Args().size() == 2); // The second argument is either 'id' , 'delta', or 'mask'. - // All must be bound by [0, 128) + // All must be bound by [0, subgroup_size) auto* arg2 = builtin->Args()[1]; // arg2 must be an unsigned integer scalar, so bitcast if necessary. if (arg2->Type()->IsSignedIntegerScalar()) { @@ -1261,15 +1304,18 @@ } /// Polyfill a `subgroupShuffleX` builtin call with one that has clamped the arg2 param - if (clamp_subgroup_shuffle) { - auto* shuffle_id = builtin->Args()[1]; - auto* mask_max_subgroup_size = - b.Constant(core::u32(tint::internal_limits::kMaxSubgroupSize - 1)); - b.InsertBefore(builtin, [&] { - auto* clamp_via_masking_and = b.And(shuffle_id, mask_max_subgroup_size);
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/src/tint/lang/spirv/writer/raise/builtin_polyfill_test.cc b/src/tint/lang/spirv/writer/raise/builtin_polyfill_test.cc
index 6b406ed..5004c46 100644
--- a/src/tint/lang/spirv/writer/raise/builtin_polyfill_test.cc
+++ b/src/tint/lang/spirv/writer/raise/builtin_polyfill_test.cc
@@ -1387,19 +1387,22 @@
}
TEST_F(SpirvWriter_BuiltinPolyfillTest, SubgroupShuffleDown_Clamped) {
- auto* val = b.FunctionParam("val", ty.i32());
- auto* delta = b.FunctionParam("delta", ty.u32());
auto* func = b.Function("foo", ty.i32());
- func->SetParams({val, delta});
+ func->SetStage(core::ir::Function::PipelineStage::kFragment);
+ func->SetReturnLocation(0);
b.Append(func->Block(), [&] {
+ auto* val = b.Let("val", 1_i);
+ auto* delta = b.Let("delta", 1_u);
auto* result = b.Call(ty.i32(), core::BuiltinFn::kSubgroupShuffleDown, val, delta);
b.Return(func, result);
});
auto* src = R"(
-%foo = func(%val:i32, %delta:u32):i32 {
+%foo = @fragment func():i32 [@location(0)] {
$B1: {
+ %val:i32 = let 1i
+ %delta:u32 = let 1u
%4:i32 = subgroupShuffleDown %val, %delta
ret %4
}
@@ -1408,35 +1411,46 @@
EXPECT_EQ(src, str());
auto* expect = R"(
-%foo = func(%val:i32, %delta:u32):i32 {
- $B1: {
- %4:u32 = and %delta, 127u
- %5:i32 = subgroupShuffleDown %val, %4
- ret %5
+$B1: { # root
+ %tint_subgroup_size_mask:ptr<private, u32, read_write> = var undef
+}
+
+%foo = @fragment func(%tint_subgroup_size:u32 [@subgroup_size]):i32 [@location(0)] {
+ $B2: {
+ %4:u32 = sub %tint_subgroup_size, 1u
+ store %tint_subgroup_size_mask, %4
+ %val:i32 = let 1i
+ %delta:u32 = let 1u
+ %7:u32 = load %tint_subgroup_size_mask
+ %8:u32 = and %delta, %7
+ %9:i32 = subgroupShuffleDown %val, %8
+ ret %9
}
}
)";
- PolyfillConfig config{.subgroup_shuffle_clamped = true};
- Run(BuiltinPolyfill, config);
+ Run(BuiltinPolyfill, PolyfillConfig{});
EXPECT_EQ(expect, str());
}
TEST_F(SpirvWriter_BuiltinPolyfillTest, SubgroupShuffleDown_SignedDelta_Clamped) {
- auto* val = b.FunctionParam("val", ty.i32());
- auto* delta = b.FunctionParam("delta", ty.u32());
auto* func = b.Function("foo", ty.i32());
- func->SetParams({val, delta});
+ func->SetStage(core::ir::Function::PipelineStage::kFragment);
+ func->SetReturnLocation(0);
b.Append(func->Block(), [&] {
+ auto* val = b.Let("val", 1_i);
+ auto* delta = b.Let("delta", 1_u);
auto* result = b.Call(ty.i32(), core::BuiltinFn::kSubgroupShuffleDown, val, delta);
b.Return(func, result);
});
auto* src = R"(
-%foo = func(%val:i32, %delta:u32):i32 {
+%foo = @fragment func():i32 [@location(0)] {
$B1: {
+ %val:i32 = let 1i
+ %delta:u32 = let 1u
%4:i32 = subgroupShuffleDown %val, %delta
ret %4
}
@@ -1445,98 +1459,46 @@
EXPECT_EQ(src, str());
auto* expect = R"(
-%foo = func(%val:i32, %delta:u32):i32 {
- $B1: {
- %4:u32 = and %delta, 127u
- %5:i32 = subgroupShuffleDown %val, %4
- ret %5
+$B1: { # root
+ %tint_subgroup_size_mask:ptr<private, u32, read_write> = var undef
+}
+
+%foo = @fragment func(%tint_subgroup_size:u32 [@subgroup_size]):i32 [@location(0)] {
+ $B2: {
+ %4:u32 = sub %tint_subgroup_size, 1u
+ store %tint_subgroup_size_mask, %4
+ %val:i32 = let 1i
+ %delta:u32 = let 1u
+ %7:u32 = load %tint_subgroup_size_mask
+ %8:u32 = and %delta, %7
+ %9:i32 = subgroupShuffleDown %val, %8
+ ret %9
}
}
)";
- PolyfillConfig config{.subgroup_shuffle_clamped = true};
- Run(BuiltinPolyfill, config);
-
- EXPECT_EQ(expect, str());
-}
-
-TEST_F(SpirvWriter_BuiltinPolyfillTest, SubgroupShuffleDown_Unclamped) {
- auto* val = b.FunctionParam("val", ty.i32());
- auto* delta = b.FunctionParam("delta", ty.u32());
- auto* func = b.Function("foo", ty.i32());
- func->SetParams({val, delta});
-
- b.Append(func->Block(), [&] {
- auto* result = b.Call(ty.i32(), core::BuiltinFn::kSubgroupShuffleDown, val, delta);
- b.Return(func, result);
- });
-
- auto* src = R"(
-%foo = func(%val:i32, %delta:u32):i32 {
- $B1: {
- %4:i32 = subgroupShuffleDown %val, %delta
- ret %4
- }
-}
-)";
- EXPECT_EQ(src, str());
-
- PolyfillConfig config{.subgroup_shuffle_clamped = false};
- Run(BuiltinPolyfill, config);
-
- EXPECT_EQ(src, str());
-}
-
-TEST_F(SpirvWriter_BuiltinPolyfillTest, SubgroupShuffleDown_SignedDelta_Unclamped) {
- auto* val = b.FunctionParam("val", ty.i32());
- auto* delta = b.FunctionParam("delta", ty.u32());
- auto* func = b.Function("foo", ty.i32());
- func->SetParams({val, delta});
-
- b.Append(func->Block(), [&] {
- auto* result = b.Call(ty.i32(), core::BuiltinFn::kSubgroupShuffleDown, val, delta);
- b.Return(func, result);
- });
-
- auto* src = R"(
-%foo = func(%val:i32, %delta:u32):i32 {
- $B1: {
- %4:i32 = subgroupShuffleDown %val, %delta
- ret %4
- }
-}
-)";
- EXPECT_EQ(src, str());
-
- auto* expect = R"(
-%foo = func(%val:i32, %delta:u32):i32 {
- $B1: {
- %4:i32 = subgroupShuffleDown %val, %delta
- ret %4
- }
-}
-)";
-
- PolyfillConfig config{.subgroup_shuffle_clamped = false};
- Run(BuiltinPolyfill, config);
+ Run(BuiltinPolyfill, PolyfillConfig{});
EXPECT_EQ(expect, str());
}
TEST_F(SpirvWriter_BuiltinPolyfillTest, SubgroupShuffleUp_Clamped) {
- auto* val = b.FunctionParam("val", ty.i32());
- auto* delta = b.FunctionParam("delta", ty.u32());
auto* func = b.Function("foo", ty.i32());
- func->SetParams({val, delta});
+ func->SetStage(core::ir::Function::PipelineStage::kFragment);
+ func->SetReturnLocation(0);
b.Append(func->Block(), [&] {
+ auto* val = b.Let("val", 1_i);
+ auto* delta = b.Let("delta", 1_u);
auto* result = b.Call(ty.i32(), core::BuiltinFn::kSubgroupShuffleUp, val, delta);
b.Return(func, result);
});
auto* src = R"(
-%foo = func(%val:i32, %delta:u32):i32 {
+%foo = @fragment func():i32 [@location(0)] {
$B1: {
+ %val:i32 = let 1i
+ %delta:u32 = let 1u
%4:i32 = subgroupShuffleUp %val, %delta
ret %4
}
@@ -1545,35 +1507,46 @@
EXPECT_EQ(src, str());
auto* expect = R"(
-%foo = func(%val:i32, %delta:u32):i32 {
- $B1: {
- %4:u32 = and %delta, 127u
- %5:i32 = subgroupShuffleUp %val, %4
- ret %5
+$B1: { # root
+ %tint_subgroup_size_mask:ptr<private, u32, read_write> = var undef
+}
+
+%foo = @fragment func(%tint_subgroup_size:u32 [@subgroup_size]):i32 [@location(0)] {
+ $B2: {
+ %4:u32 = sub %tint_subgroup_size, 1u
+ store %tint_subgroup_size_mask, %4
+ %val:i32 = let 1i
+ %delta:u32 = let 1u
+ %7:u32 = load %tint_subgroup_size_mask
+ %8:u32 = and %delta, %7
+ %9:i32 = subgroupShuffleUp %val, %8
+ ret %9
}
}
)";
- PolyfillConfig config{.subgroup_shuffle_clamped = true};
- Run(BuiltinPolyfill, config);
+ Run(BuiltinPolyfill, PolyfillConfig{});
EXPECT_EQ(expect, str());
}
TEST_F(SpirvWriter_BuiltinPolyfillTest, SubgroupShuffleUp_SignedDelta_Clamped) {
- auto* val = b.FunctionParam("val", ty.i32());
- auto* delta = b.FunctionParam("delta", ty.u32());
auto* func = b.Function("foo", ty.i32());
- func->SetParams({val, delta});
+ func->SetStage(core::ir::Function::PipelineStage::kFragment);
+ func->SetReturnLocation(0);
b.Append(func->Block(), [&] {
+ auto* val = b.Let("val", 1_i);
+ auto* delta = b.Let("delta", 1_u);
auto* result = b.Call(ty.i32(), core::BuiltinFn::kSubgroupShuffleUp, val, delta);
b.Return(func, result);
});
auto* src = R"(
-%foo = func(%val:i32, %delta:u32):i32 {
+%foo = @fragment func():i32 [@location(0)] {
$B1: {
+ %val:i32 = let 1i
+ %delta:u32 = let 1u
%4:i32 = subgroupShuffleUp %val, %delta
ret %4
}
@@ -1582,62 +1555,46 @@
EXPECT_EQ(src, str());
auto* expect = R"(
-%foo = func(%val:i32, %delta:u32):i32 {
- $B1: {
- %4:u32 = and %delta, 127u
- %5:i32 = subgroupShuffleUp %val, %4
- ret %5
+$B1: { # root
+ %tint_subgroup_size_mask:ptr<private, u32, read_write> = var undef
+}
+
+%foo = @fragment func(%tint_subgroup_size:u32 [@subgroup_size]):i32 [@location(0)] {
+ $B2: {
+ %4:u32 = sub %tint_subgroup_size, 1u
+ store %tint_subgroup_size_mask, %4
+ %val:i32 = let 1i
+ %delta:u32 = let 1u
+ %7:u32 = load %tint_subgroup_size_mask
+ %8:u32 = and %delta, %7
+ %9:i32 = subgroupShuffleUp %val, %8
+ ret %9
}
}
)";
- PolyfillConfig config{.subgroup_shuffle_clamped = true};
- Run(BuiltinPolyfill, config);
+ Run(BuiltinPolyfill, PolyfillConfig{});
EXPECT_EQ(expect, str());
... (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