CVE-2026-79069
Overview
Background
- Tint MSL writer
- Tint is the WGSL shader compiler used by Dawn (Chrome’s WebGPU implementation). On macOS its MSL backend lowers WGSL through Tint IR into Metal Shading Language, which Apple’s Metal compiler (
MTLCompilerService) then compiles into GPU code. - Raise transforms
- IR-to-IR passes, run in
src/tint/lang/msl/writer/raise/raise.ccbefore MSL is printed, that rewrite the program into a form that is safe or acceptable for the Metal toolchain. Some of them exist only to work around driver bugs. - `msl.volatile_zero` (`BuiltinFn::kVolatileZero`)
- a value the driver cannot constant-fold. It is emitted as
volatile constexpr constant uint tint_volatile_zero = 0u;, so a condition liketint_volatile_zero == 0uis always true at run time but opaque to the compiler’s flow analysis. - Return nested in a `switch`
- a WGSL
returnthat appears directly inside aswitchcase block, as in the regression shader’scase 2i: { return; }.
Root Cause Analysis
The defect is not in Chrome’s own code. Per the commit message and header comment, Apple’s MTLCompilerService has a “flow analyzer crash” when MSL contains an early return directly inside a switch case. Tint’s MSL writer passed such returns straight through (the removed lines in the .expected.msl files show a bare return; in case blocks), so any WebGPU shader with this shape reached the buggy driver path. The advisory classifies the result as memory corruption.
The patch itself only shows that the driver crashed, so the exact corruption inside the Apple compiler is not visible here.
The fix adds a new raise pass, SwitchReturn, that runs after ModuleConstant and before the final cleanup transforms. It collects every core::ir::Return whose block’s parent is a core::ir::Switch, inserts in front of each one an if (msl.volatile_zero == 0u) whose true block holds an equivalent Return (with the value, if any), adds an exit_switch after it, and destroys the original Return. Run-time behaviour is unchanged. Only the shape of the code the driver sees changes, so its flow analysis no longer hits the failing pattern. This is a defensive workaround for a third-party driver bug, not a correction of a Tint logic error.
return directly inside a switch case) that Apple’s Metal compiler mishandles. The fix rewrites every such return behind an opaque, always-true volatile_zero guard, so the driver never sees the problematic pattern.Attack Path
- Serve WebGPU content on macOS
A web page uses WebGPU, which Dawn implements on its Metal backend on macOS, so Tint’s MSL writer and Apple’s
MTLCompilerServiceare in the compile path. Other platforms and backends are not affected by this workaround. - Submit a shader with a return inside a switch
The page creates a shader module and pipeline whose WGSL contains a
switchwith a case that doesreturn;, like theSwitchReturnMTLCompilerServiceCrashregression shader thatdevice.CreateComputePipeline()compiles. - Tint passes the pattern through
Before the patch, the MSL output kept the bare
returninside the case block and handed it to the Metal compiler. - Driver flow analyzer misbehaves
MTLCompilerServicecrashes while analyzing the shader. The advisory reports memory corruption. Whether an attacker can control that corruption, and what the compiler-service process can then reach, is (inferred).
Impact Assessment
MTLCompilerService) through a crafted WGSL shader, which Chrome passes on via the GPU process. Beyond a crash, any controlled exploitation of that process, or impact on Chrome’s GPU process, is (inferred). The patch only documents a driver crash and adds a source-level workaround. The vendor advisory rates this high severity and does not report in-the-wild exploitation; NVD scores it CVSS 8.8. Preconditions are macOS, the Metal backend, and WebGPU availability.Changed Functions
| Function | Change | Notes |
|---|---|---|
forsrc/tint/lang/msl/writer/raise/switch_return.cc |
modified | |
Modulesrc/tint/lang/msl/writer/raise/switch_return.h |
modified | |
switchtest/tint/switch/switch_only_default.wgsl.expected.msl |
modified | |
TEST_Psrc/dawn/tests/end2end/ComputeFlowControlTests.cpp |
modified | |
switchsrc/dawn/tests/end2end/ComputeFlowControlTests.cpp |
modified |
Files Changed
src/tint/lang/msl/writer/raise/switch_return.ccsrc/tint/lang/msl/writer/raise/switch_return_test.cctest/tint/switch/switch.wgsl.expected.mslsrc/tint/lang/msl/writer/raise/BUILD.gntest/tint/switch/switch_nested.wgsl.expected.mslsrc/tint/lang/msl/writer/raise/BUILD.bazeltest/tint/switch/switch_multi_selector.wgsl.expected.mslsrc/tint/lang/msl/writer/raise/switch_return.hsrc/tint/lang/msl/writer/raise/BUILD.cmakesrc/dawn/tests/end2end/ComputeFlowControlTests.cppsrc/tint/lang/msl/writer/raise/raise.cctest/tint/switch/switch_only_default.wgsl.expected.msl
Audit Directions
- Driver-hostile control-flow shapes in other backendsCheck whether the HLSL (FXC/DXC), SPIR-V and GLSL writers emit early
return,breakorcontinueinsideswitchor nested loops in ways known to trip vendor compilers, and whether equivalent workarounds exist. - Completeness of the pattern match
Processonly matches aReturnwhose immediateBlock()->Parent()is aSwitch. Audit whether returns nested deeper (for example in aniforloopinside a case) or produced by later transforms still reach the driver in the original problematic form. - Transform orderingThe new pass runs before
RemoveTerminatorArgsand other final passes. Verify that no later raise transform re-introduces a barereturninside a switch or folds away thevolatile_zeroguard.
Patch
From c178159160404ec25f57ab8dd8ba450de75ce140 Mon Sep 17 00:00:00 2001 From: Natalie Chouinard <[email protected]> Date: Thu, 16 Jul 2026 17:53:11 -0700 Subject: [PATCH] [tint][msl] Add SwitchReturn IR transform Adds an MSL transform pass that wraps early returns nested inside switch blocks with a volatile-guarded conditional. This works around a driver bug in MTLCompilerService on macOS. Bug: 508638064 Change-Id: I8c8b7cb0a3710f855499fdfa94872117a8d1bd23 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/325075 Reviewed-by: dan sinclair <[email protected]> Commit-Queue: Natalie Chouinard <[email protected]> --- diff --git a/src/tint/lang/msl/writer/raise/BUILD.cmake b/src/tint/lang/msl/writer/raise/BUILD.cmake index d3772ca..db2ac9d 100644 --- a/src/tint/lang/msl/writer/raise/BUILD.cmake +++ b/src/tint/lang/msl/writer/raise/BUILD.cmake @@ -63,6 +63,8 @@ lang/msl/writer/raise/shader_io.h lang/msl/writer/raise/simd_ballot.cc lang/msl/writer/raise/simd_ballot.h + lang/msl/writer/raise/switch_return.cc + lang/msl/writer/raise/switch_return.h lang/msl/writer/raise/validate_subgroup_matrix.cc lang/msl/writer/raise/validate_subgroup_matrix.h ) @@ -116,6 +118,7 @@ lang/msl/writer/raise/module_scope_vars_test.cc lang/msl/writer/raise/shader_io_test.cc lang/msl/writer/raise/simd_ballot_test.cc + lang/msl/writer/raise/switch_return_test.cc lang/msl/writer/raise/validate_subgroup_matrix_test.cc ) diff --git a/src/tint/lang/msl/writer/raise/raise.cc b/src/tint/lang/msl/writer/raise/raise.cc index e340bcf..71400bc 100644 --- a/src/tint/lang/msl/writer/raise/raise.cc +++ b/src/tint/lang/msl/writer/raise/raise.cc @@ -72,6 +72,7 @@ #include "src/tint/lang/msl/writer/raise/module_scope_vars.h" #include "src/tint/lang/msl/writer/raise/shader_io.h" #include "src/tint/lang/msl/writer/raise/simd_ballot.h" +#include "src/tint/lang/msl/writer/raise/switch_return.h" #include "src/tint/lang/msl/writer/raise/validate_subgroup_matrix.h" namespace tint::msl::writer { @@ -302,6 +303,8 @@ options.workarounds.disable_module_constant_f16}; TINT_CHECK_RESULT(raise::ModuleConstant(module, module_const_config)); + TINT_CHECK_RESULT(raise::SwitchReturn(module)); + // These transforms need to be run last as various transforms introduce terminator arguments, // naming conflicts, and expressions that need to be explicitly not inlined. TINT_CHECK_RESULT(core::ir::transform::RemoveTerminatorArgs(module)); diff --git a/src/tint/lang/msl/writer/raise/switch_return.cc b/src/tint/lang/msl/writer/raise/switch_return.cc new file mode 100644 index 0000000..ca90580 --- /dev/null +++ b/src/tint/lang/msl/writer/raise/switch_return.cc @@ -0,0 +1,97 @@ +// Copyright 2026 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/msl/writer/raise/switch_return.h" + +#include "src/tint/lang/core/ir/builder.h" +#include "src/tint/lang/core/ir/module.h" +#include "src/tint/lang/core/ir/validator.h" +#include "src/tint/lang/core/type/u32.h" +#include "src/tint/lang/msl/ir/builtin_call.h" + +namespace tint::msl::writer::raise { + +namespace { + +/// PIMPL state for the transform. +struct State { + /// The IR module. + core::ir::Module& ir; + + /// The IR builder. + core::ir::Builder b{ir}; + + /// The type manager. + core::type::Manager& ty{ir.Types()}; + + /// Process the module. + void Process() { + // Find all return instructions inside switches. + Vector<core::ir::Return*, 8> returns_to_wrap; + + for (auto* fn : ir.functions) { + fn->ForEachUseSorted([&](const core::ir::Usage& usage) { + if (auto* ret = usage.instruction->As<core::ir::Return>()) { + auto* parent = ret->Block()->Parent(); + if (parent && parent->Is<core::ir::Switch>()) { + returns_to_wrap.Push(ret); + } + } + }); + } + + // Wrap return in volatile zero conditional to work around a driver bug + // (crbug.com/508638064). + for (auto* ret : returns_to_wrap) { + b.InsertBefore(ret, [&] { + auto* zero = b.Call<msl::ir::BuiltinCall>(ty.u32(), msl::BuiltinFn::kVolatileZero); + auto* cond = b.If(b.Equal(zero, b.Constant(core::u32(0)))); + b.Append(cond->True(), [&] { + if (ret->Value()) { + b.Return(ret->Func(), ret->Value()); + } else { + b.Return(ret->Func()); + } + }); + b.Exit(ret->Block()->Parent()); + }); + ret->Destroy(); + } + } +}; + +} // namespace + +Result<SuccessType> SwitchReturn(core::ir::Module& ir) { + core::ir::AssertValid(ir, "before msl.SwitchReturn"); + + State{ir}.Process(); + + return Success; +} + +} // namespace tint::msl::writer::raise diff --git a/src/tint/lang/msl/writer/raise/switch_return.h b/src/tint/lang/msl/writer/raise/switch_return.h new file mode 100644 index 0000000..3d06f84 --- /dev/null +++ b/src/tint/lang/msl/writer/raise/switch_return.h @@ -0,0 +1,49 @@ +// Copyright 2026 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. + +#ifndef SRC_TINT_LANG_MSL_WRITER_RAISE_SWITCH_RETURN_H_ +#define SRC_TINT_LANG_MSL_WRITER_RAISE_SWITCH_RETURN_H_ + +#include "src/tint/utils/result.h" + +// Forward declarations +namespace tint::core::ir { +class Module; +} // namespace tint::core::ir + +namespace tint::msl::writer::raise { + +/// SwitchReturn is a transform that wraps early returns inside switches with a local +/// volatile conditional block, to bypass a flow analyzer crash in Apple's MTLCompilerService +/// (crbug.com/508638064). +/// @param module the module to transform +/// @returns success or failure +Result<SuccessType> SwitchReturn(core::ir::Module& ir); + +} // namespace tint::msl::writer::raise + +#endif // SRC_TINT_LANG_MSL_WRITER_RAISE_SWITCH_RETURN_H_ diff --git a/test/tint/switch/switch.wgsl.expected.msl b/test/tint/switch/switch.wgsl.expected.msl index a85fb4a..96ce133 100644 --- a/test/tint/switch/switch.wgsl.expected.msl +++ b/test/tint/switch/switch.wgsl.expected.msl @@ -1,6 +1,8 @@ #include <metal_stdlib> using namespace metal; +volatile constexpr constant uint tint_volatile_zero = 0u; + [[max_total_threads_per_threadgroup(1)]] kernel void a() { int a_1 = 0; @@ -11,7 +13,10 @@ } case 1: { - return; + if (( tint_volatile_zero == 0u)) { + return; + } + break; } default: { diff --git a/test/tint/switch/switch_multi_selector.wgsl.expected.msl b/test/tint/switch/switch_multi_selector.wgsl.expected.msl index 20c9f4a..a8e5df9 100644 --- a/test/tint/switch/switch_multi_selector.wgsl.expected.msl +++ b/test/tint/switch/switch_multi_selector.wgsl.expected.msl @@ -1,6 +1,8 @@ #include <metal_stdlib> using namespace metal; +volatile constexpr constant uint tint_volatile_zero = 0u; + [[max_total_threads_per_threadgroup(1)]] kernel void a() { int a_1 = 0; @@ -14,7 +16,10 @@ case 1: default: { - return; + if (( tint_volatile_zero == 0u)) { + return; + } + break; } } } diff --git a/test/tint/switch/switch_nested.wgsl.expected.msl b/test/tint/switch/switch_nested.wgsl.expected.msl index 4c56821..5ef2009 100644 --- a/test/tint/switch/switch_nested.wgsl.expected.msl +++ b/test/tint/switch/switch_nested.wgsl.expected.msl @@ -1,6 +1,8 @@ #include <metal_stdlib> using namespace metal; +volatile constexpr constant uint tint_volatile_zero = 0u; + [[max_total_threads_per_threadgroup(1)]] kernel void a() { int a_1 = 0; @@ -28,10 +30,16 @@ } default: { - return; + if (( tint_volatile_zero == 0u)) { + return; + } + break; } } - return; + if (( tint_volatile_zero == 0u)) { + return; + } + break; } } break; @@ -39,7 +47,10 @@ case 1: default: { - return; + if (( tint_volatile_zero == 0u)) { + return; + } + break; } } } diff --git a/test/tint/switch/switch_only_default.wgsl.expected.msl b/test/tint/switch/switch_only_default.wgsl.expected.msl index bf98158..ceed559 100644 --- a/test/tint/switch/switch_only_default.wgsl.expected.msl +++ b/test/tint/switch/switch_only_default.wgsl.expected.msl @@ -1,13 +1,18 @@ #include <metal_stdlib> using namespace metal; +volatile constexpr constant uint tint_volatile_zero = 0u; + [[max_total_threads_per_threadgroup(1)]] kernel void a() { int a_1 = 0; switch(a_1) { default: { - return; + if (( tint_volatile_zero == 0u)) { + return; + } + break; } } /* unreachable */ diff --git a/src/dawn/tests/end2end/ComputeFlowControlTests.cpp b/src/dawn/tests/end2end/ComputeFlowControlTests.cpp index d29b228..cf9ebcc 100644 --- a/src/dawn/tests/end2end/ComputeFlowControlTests.cpp +++ b/src/dawn/tests/end2end/ComputeFlowControlTests.cpp @@ -513,6 +513,47 @@ RunTest(shader, inputs, expected); } +// This is a regression test for a crash in MTLCompilerService on macOS (crbug.com/508638064). +TEST_P(ComputeFlowControlTests, SwitchReturnMTLCompilerServiceCrash) { + const char* shader = R"( +@group(0) @binding(0) var<uniform> u: i32; +@group(0) @binding(1) var<storage, read_write> s: i32; + +fn foo(){ + switch (s) { + case 0i: {} + case 2i: { + return; + } + case 4i: {} + default: { + s = s / u; + } + } + switch (s) { + case 0i: { + s = 0; + } + case 1i: {} + case 4i: {} + default: { + s = 0; + } + } +} + +@compute @workgroup_size(1) +fn main() { + foo(); + foo(); +} +)"; + + wgpu::ComputePipelineDescriptor csDesc; + csDesc.compute.module = utils::CreateShaderModule(device, shader); + device.CreateComputePipeline(&csDesc); +} + DAWN_INSTANTIATE_TEST(ComputeFlowControlTests, D3D11Backend(), D3D12Backend(), diff --git a/src/tint/lang/msl/writer/raise/switch_return_test.cc b/src/tint/lang/msl/writer/raise/switch_return_test.cc new file mode 100644 index 0000000..ca6f943 --- /dev/null +++ b/src/tint/lang/msl/writer/raise/switch_return_test.cc @@ -0,0 +1,152 @@ +// Copyright 2026 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, ... (file diff truncated)
Regression Test / PoC
diff --git a/src/dawn/tests/end2end/ComputeFlowControlTests.cpp b/src/dawn/tests/end2end/ComputeFlowControlTests.cpp
index d29b228..cf9ebcc 100644
--- a/src/dawn/tests/end2end/ComputeFlowControlTests.cpp
+++ b/src/dawn/tests/end2end/ComputeFlowControlTests.cpp
@@ -513,6 +513,47 @@
RunTest(shader, inputs, expected);
}
+// This is a regression test for a crash in MTLCompilerService on macOS (crbug.com/508638064).
+TEST_P(ComputeFlowControlTests, SwitchReturnMTLCompilerServiceCrash) {
+ const char* shader = R"(
+@group(0) @binding(0) var<uniform> u: i32;
+@group(0) @binding(1) var<storage, read_write> s: i32;
+
+fn foo(){
+ switch (s) {
+ case 0i: {}
+ case 2i: {
+ return;
+ }
+ case 4i: {}
+ default: {
+ s = s / u;
+ }
+ }
+ switch (s) {
+ case 0i: {
+ s = 0;
+ }
+ case 1i: {}
+ case 4i: {}
+ default: {
+ s = 0;
+ }
+ }
+}
+
+@compute @workgroup_size(1)
+fn main() {
+ foo();
+ foo();
+}
+)";
+
+ wgpu::ComputePipelineDescriptor csDesc;
+ csDesc.compute.module = utils::CreateShaderModule(device, shader);
+ device.CreateComputePipeline(&csDesc);
+}
+
DAWN_INSTANTIATE_TEST(ComputeFlowControlTests,
D3D11Backend(),
D3D12Backend(),
diff --git a/src/tint/lang/msl/writer/raise/switch_return_test.cc b/src/tint/lang/msl/writer/raise/switch_return_test.cc
new file mode 100644
index 0000000..ca6f943
--- /dev/null
+++ b/src/tint/lang/msl/writer/raise/switch_return_test.cc
@@ -0,0 +1,152 @@
+// Copyright 2026 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/msl/writer/raise/switch_return.h"
+
+#include <utility>
+
+#include "src/tint/lang/core/fluent_types.h"
+#include "src/tint/lang/core/ir/transform/helper_test.h"
+
+using namespace tint::core::fluent_types; // NOLINT
+using namespace tint::core::number_suffixes; // NOLINT
+
+namespace tint::msl::writer::raise {
+namespace {
+
+using MslWriter_SwitchReturnTest = core::ir::transform::TransformTest;
+
+TEST_F(MslWriter_SwitchReturnTest, ReturnInsideSwitch) {
+ auto* func = b.Function("foo", ty.void_());
+ b.Append(func->Block(), [&] {
+ auto* sw = b.Switch(1_i);
+ b.Append(b.Case(sw, {b.Constant(0_i)}), [&] { b.Return(func); });
+ b.Append(b.DefaultCase(sw), [&] { b.ExitSwitch(sw); });
+ b.Return(func);
+ });
+
+ auto* src = R"(
+%foo = func():void {
+ $B1: {
+ switch 1i [c: (0i, $B2), c: (default, $B3)] { # switch_1
+ $B2: { # case
+ ret
+ }
+ $B3: { # case
+ exit_switch # switch_1
+ }
+ }
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%foo = func():void {
+ $B1: {
+ switch 1i [c: (0i, $B2), c: (default, $B3)] { # switch_1
+ $B2: { # case
+ %2:u32 = msl.volatile_zero
+ %3:bool = eq %2, 0u
+ if %3 [t: $B4] { # if_1
+ $B4: { # true
+ ret
+ }
+ }
+ exit_switch # switch_1
+ }
+ $B3: { # case
+ exit_switch # switch_1
+ }
+ }
+ ret
+ }
+}
+)";
+
+ Run(SwitchReturn);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(MslWriter_SwitchReturnTest, ReturnValueInsideSwitch) {
+ auto* func = b.Function("foo", ty.i32());
+ b.Append(func->Block(), [&] {
+ auto* sw = b.Switch(1_i);
+ b.Append(b.Case(sw, {b.Constant(0_i)}), [&] { b.Return(func, 42_i); });
+ b.Append(b.DefaultCase(sw), [&] { b.ExitSwitch(sw); });
+ b.Return(func, 0_i);
+ });
+
+ auto* src = R"(
+%foo = func():i32 {
+ $B1: {
+ switch 1i [c: (0i, $B2), c: (default, $B3)] { # switch_1
+ $B2: { # case
+ ret 42i
+ }
+ $B3: { # case
+ exit_switch # switch_1
+ }
+ }
+ ret 0i
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+%foo = func():i32 {
+ $B1: {
+ switch 1i [c: (0i, $B2), c: (default, $B3)] { # switch_1
+ $B2: { # case
+ %2:u32 = msl.volatile_zero
+ %3:bool = eq %2, 0u
+ if %3 [t: $B4] { # if_1
+ $B4: { # true
+ ret 42i
+ }
+ }
+ exit_switch # switch_1
+ }
+ $B3: { # case
+ exit_switch # switch_1
+ }
+ }
+ ret 0i
+ }
+}
+)";
+
+ Run(SwitchReturn);
+
+ EXPECT_EQ(expect, str());
+}
+
+} // namespace
+} // namespace tint::msl::writer::raise
Original Bug Report
[macOS] Memory corruption in MSLCompilerService during WGSL compilation
Vulnerability details
On macOS Chrome, compiling the WGSL shader included in this bug report crashes MTLCompilerService with a SIGSEGV at a high address.
The GPU process does not crash (dev console just shows XPC_ERROR_CONNECTION_INTERRUPTED). However, it crashes MTLCompilerService that the GPU process communicates with via XPC. Here is an issue that found something similar: https://issues.chromium.org/issues/40074630
Version
Device: Mac Mini 2024 (Apple M4) 16GB MU9D3LL/A
Chrome version: 147.0.7727.138 (Official Build) (arm64)
OS: macOS Tahoe 26.4.1 (Build 25E253)
Reproduction case
After opening poc.html in Chrome, open Console.App to see the trace. Alternatively, run sudo lldb -n MTLCompilerService --wait-for before opening poc.html.
The WGSL shader from poc.html is
@group(0) @binding(0)
var<uniform> u: i32;
@group(0) @binding(1)
var<storage, read_write> s: i32;
fn foo(){
switch (s) {
case 0i: {}
case 2i: {
return;
}
case 4i: {}
default: {
s = s / u;
}
}
switch (s) {
case 0i: {
s = 0;
}
case 1i: {}
case 4i: {}
default: {
s = 0;
}
}
}
@compute @workgroup_size(1u)
fn main() {
foo();
foo();
}
The MSL dump from Chrome is attached as poc.msl
Crash details
The stack trace from MTLCompilerService is:
% sudo lldb -n MTLCompilerService --wait-for
(lldb) bt
* thread #3, queue = 'com.apple.root.default-qos.overcommit', stop reason = EXC_BAD_ACCESS (code=1, address=0x346fabb90)
* frame #0: 0x000000024d2ee330 libLLVM.dylib`llvm::FlowAnalyzer::evaluateSkipTargets(llvm::BasePtr<unsigned int, llvm::NodePtrToken>, llvm::BasePtr<unsigned int, llvm::NodePtrToken>, llvm::BitVector&, llvm::ControlGraph<llvm::MachineCFGTraits> const&, llvm::CFGUnloopifier<llvm::MachineCFGTraits> const&) + 324
frame #1: 0x000000024d2ebc44 libLLVM.dylib`llvm::FlowAnalyzer::analyze(llvm::ControlGraph<llvm::MachineCFGTraits> const&, llvm::CFGUnloopifier<llvm::MachineCFGTraits> const&, llvm::MachineLoopInfo const&, llvm::GPUFlowEmitter const*, llvm::TargetInstrInfo const*) + 1908
frame #2: 0x000000024d2eb49c libLLVM.dylib`llvm::FlowAnalyzer::FlowAnalyzer(llvm::ControlGraph<llvm::MachineCFGTraits> const&, llvm::CFGUnloopifier<llvm::MachineCFGTraits> const&, llvm::MachineLoopInfo const&, llvm::GPUFlowEmitter const*, llvm::TargetInstrInfo const*) + 128
frame #3: 0x000000024c8b28cc libLLVM.dylib`___lldb_unnamed_symbol_2432ea684 + 584
frame #4: 0x000000024cf496bc libLLVM.dylib`llvm::MachineFunctionPass::runOnFunction(llvm::Function&) + 368
frame #5: 0x000000024d1bf78c libLLVM.dylib`llvm::FPPassManager::runOnFunction(llvm::Function&) + 676
frame #6: 0x000000024cbf1724 libLLVM.dylib`___lldb_unnamed_symbol_2436293a4 + 896
frame #7: 0x000000024d1c0070 libLLVM.dylib`llvm::legacy::PassManagerImpl::run(llvm::Module&) + 788
frame #8: 0x000000024cb95b28 libLLVM.dylib`___lldb_unnamed_symbol_2435cd53c + 1516
frame #9: 0x000000024cb8fd14 libLLVM.dylib`___lldb_unnamed_symbol_2435c7940 + 980
frame #10: 0x000000024cb967e8 libLLVM.dylib`llvm::AGX::AGXCompilePlan::execute(llvm::AGX::CompileRequest&) + 176
frame #11: 0x00000001b5e7eac4 AGXCompilerCore`AGCLLVMCtx::compile(AGCLLVMObject*, llvm::Module&, AGCFastMathFlags, llvm::AGX::PipelineType, llvm::AGX::CodeGenOptions&, bool) + 1324
frame #12: 0x00000001b5ded3f4 AGXCompilerCore`AGCLLVMUserObject::compile() + 236
frame #13: 0x00000001b5efef90 AGXCompilerCore`AGCModule<AGX::Impl::ComputeProgramKey>::compileLLVMObject(AGCTargetContext*, char**, unsigned long*, char**, char**, unsigned long*) + 1040
frame #14: 0x00000001b5efd8c0 AGXCompilerCore`AIRNTEmitPipelineImageInternal(void*, void*, unsigned int, std::__1::unique_ptr<AGCModuleAccessor, std::__1::default_delete<AGCModuleAccessor>>, unsigned int, unsigned int, AIRFunctionScript const*, char const*, char**, unsigned long*, char**, unsigned long*, char**) + 4180
frame #15: 0x00000001b5f038a0 AGXCompilerCore`AIRNTEmitPipelineImageWithModuleRef + 164
frame #16: 0x0000000260639758 MTLCompiler`MTLCompilerPluginInterface::airntEmitPipelineImage(void*, llvm::Module*, unsigned int, unsigned int, unsigned int, unsigned int, AIRFunctionScript const*, char**, unsigned long*, char**, unsigned long*, char**) + 132
frame #17: 0x0000000260633e5c MTLCompiler`MTLCompilerObject::backendCompileModule(BinaryRequestData&, BackendCompilationOutput&, unsigned long long, std::__1::vector<CompileTimeData, std::__1::allocator<CompileTimeData>>&) + 780
frame #18: 0x0000000260639ec8 MTLCompiler`MTLCompilerObject::backendCompileExecutableRequest(BinaryRequestData&) + 556
frame #19: 0x000000026063de18 MTLCompiler`MTLCompilerObject::buildRequest(unsigned int, unsigned int, void const*, unsigned long, void (unsigned int, void const*, unsigned long, char const*) block_pointer) + 816
frame #20: 0x00000002606471cc MTLCompiler`split_stack_call + 24
frame #21: 0x000000026063a360 MTLCompiler`MTLCodeGenServiceBuildRequest + 324
frame #22: 0x00000001024d504c MTLCompilerService`compileRequestMain + 140
frame #23: 0x00000001024d61dc MTLCompilerService`MTLCompilerService::messageHandler(NSObject<OS_xpc_object>*) + 932
frame #24: 0x0000000189794444 libxpc.dylib`_xpc_connection_call_event_handler + 112
frame #25: 0x0000000189792cdc libxpc.dylib`_xpc_connection_mach_event + 1488
frame #26: 0x00000001899164f8 libdispatch.dylib`_dispatch_client_callout4 + 16
frame #27: 0x0000000189918e94 libdispatch.dylib`_dispatch_mach_msg_invoke + 480
frame #28: 0x0000000189904e98 libdispatch.dylib`_dispatch_lane_serial_drain + 332
frame #29: 0x0000000189919bec libdispatch.dylib`_dispatch_mach_invoke + 472
frame #30: 0x000000018990fe34 libdispatch.dylib`_dispatch_root_queue_drain_deferred_wlh + 284
frame #31: 0x000000018990f734 libdispatch.dylib`_dispatch_workloop_worker_thread + 720
frame #32: 0x0000000189ab3ec0 libsystem_pthread.dylib`_pthread_wqthread + 292
(lldb) register read
General Purpose Registers:
x0 = 0x0000000108f803f8
x1 = 0x0000000000000015
x2 = 0x0000000000000017
x3 = 0x0000000108f801c8
x4 = 0x0000000108f80b70
x5 = 0x0000000000000000
x6 = 0x0000000100000004
x7 = 0xfffff0003ffff800
x8 = 0x0000000346fabb80
x9 = 0x0000000108f80a58
x10 = 0x0000000108f80b80
x11 = 0x0000000000000000
x12 = 0x0000000000000000
x13 = 0x0000000000000015
x14 = 0x0000000000000001
x15 = 0x000000000000000a
x16 = 0x5d16800189ac1090 (0x0000000189ac1090) libsystem_platform.dylib`__bzero
x17 = 0x00000002a34179c8
x18 = 0x0000000000000000
x19 = 0x0000000108f801c8
x20 = 0x0000000108f80058
x21 = 0x0000000108f80b70
x22 = 0x0000000000000017
x23 = 0x0000000000000015
x24 = 0x0000000108f803f8
x25 = 0x0000000000000015
x26 = 0x0000000008f80ac0
x27 = 0x0000000108f80838
x28 = 0x0000000008f80ac0
fp = 0x0000000108f800f0
lr = 0xf02c80024d2ebc44 (0x000000024d2ebc44) libLLVM.dylib`llvm::FlowAnalyzer::analyze(llvm::ControlGraph<llvm::MachineCFGTraits> const&, llvm::CFGUnloopifier<llvm::MachineCFGTraits> const&, llvm::MachineLoopInfo const&, llvm::GPUFlowEmitter const*, llvm::TargetInstrInfo const*) + 1908
sp = 0x0000000108f80040
pc = 0x000000024d2ee330 libLLVM.dylib`llvm::FlowAnalyzer::evaluateSkipTargets(llvm::BasePtr<unsigned int, llvm::NodePtrToken>, llvm::BasePtr<unsigned int, llvm::NodePtrToken>, llvm::BitVector&, llvm::ControlGraph<llvm::MachineCFGTraits> const&, llvm::CFGUnloopifier<llvm::MachineCFGTraits> const&) + 324
cpsr = 0x20000000
I also reported this to Apple Security Research (OE1106044528210).