CVE-2026-15770
Overview
Files Changed
src/compiler/backend/arm/instruction-selector-arm.ccsrc/compiler/backend/arm64/instruction-selector-arm64.cctest/mjsunit/regress/wasm/regress-524792614.js
Patch
From 78afe7b553f2befacffd4fe882e0947484b093d2 Mon Sep 17 00:00:00 2001 From: Sam Parker <[email protected]> Date: Tue, 07 Jul 2026 10:11:14 +0100 Subject: [PATCH] [compiler][arm64][arm] IsOnlyUserOfNodeInSameBlock Don't try to use IsOnlyUserOfNodeInSameBlock when the continuation is anything but a branch or set as it's possible that we've already explored through the original conditional setting instruction and are trying to optimise a binop that would then have to be pulled past a use to be tied to the continuation. Bug: 524792614 Change-Id: I94a3c46799197c152873ea9f09e99ff4db4efccd Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/8024753 Reviewed-by: Nico Hartmann <[email protected]> Commit-Queue: Sam Parker-Haynes <[email protected]> Reviewed-by: Daniel Lehmann <[email protected]> Cr-Commit-Position: refs/heads/main@{#108482} --- diff --git a/src/compiler/backend/arm/instruction-selector-arm.cc b/src/compiler/backend/arm/instruction-selector-arm.cc index 5053420..3e56d63 100644 --- a/src/compiler/backend/arm/instruction-selector-arm.cc +++ b/src/compiler/backend/arm/instruction-selector-arm.cc @@ -2061,10 +2061,13 @@ cont->Overwrite(MapForFlagSettingBinop(cond)); *opcode = no_output_opcode; *node = binop; - } else if (selector->IsOnlyUserOfNodeInSameBlock(*node, binop)) { - // We can also handle the case where the {node} and the comparison are in - // the same basic block, and the comparison is the only user of {node} in - // this basic block ({node} has users in other basic blocks). + } else if ((cont->IsBranch() || cont->IsSet()) && + selector->IsOnlyUserOfNodeInSameBlock(*node, binop)) { + // We can also handle the case where the add and the compare are in the + // same basic block, and the compare is the only use of add in this basic + // block (the add has users in other basic blocks). We only do this for + // branches and sets as they can't end up breaking the schedule by pulling + // the flag-setting instruction past its users. cont->Overwrite(MapForFlagSettingBinop(cond)); *opcode = binop_opcode; *node = binop; diff --git a/src/compiler/backend/arm64/instruction-selector-arm64.cc b/src/compiler/backend/arm64/instruction-selector-arm64.cc index 7fae7f1..9cf8cbe 100644 --- a/src/compiler/backend/arm64/instruction-selector-arm64.cc +++ b/src/compiler/backend/arm64/instruction-selector-arm64.cc @@ -3553,10 +3553,13 @@ *opcode = no_output_opcode; *node = binop; *immediate_mode = binop_immediate_mode; - } else if (selector->IsOnlyUserOfNodeInSameBlock(*node, binop)) { + } else if ((cont->IsBranch() || cont->IsSet()) && + selector->IsOnlyUserOfNodeInSameBlock(*node, binop)) { // We can also handle the case where the add and the compare are in the // same basic block, and the compare is the only use of add in this basic - // block (the add has users in other basic blocks). + // block (the add has users in other basic blocks). We only do this for + // branches and sets as they can't end up breaking the schedule by pulling + // the flag-setting instruction past its users. cont->Overwrite(MapForFlagSettingBinop(cond)); *opcode = binop_opcode; *node = binop; diff --git a/test/mjsunit/regress/wasm/regress-524792614.js b/test/mjsunit/regress/wasm/regress-524792614.js new file mode 100644 index 0000000..9a90f23 --- /dev/null +++ b/test/mjsunit/regress/wasm/regress-524792614.js @@ -0,0 +1,61 @@ +// Copyright 2026 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --allow-natives-syntax + +d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js'); + +const builder = new WasmModuleBuilder(); +const g = builder.addGlobal(kWasmI32, true); + +builder.addFunction('main', makeSig([kWasmI32], [kWasmI32, kWasmI32])) + .exportAs('main') + .addLocals(kWasmI32, 2) + .addBody([ + // v2 = param 0 + 1 + kExprLocalGet, + 0, + kExprI32Const, + 1, + kExprI32Add, + kExprLocalSet, + 1, + + // v27 = 0 - v2 + kExprI32Const, + 0, + kExprLocalGet, + 1, + kExprI32Sub, + kExprLocalSet, + 2, + + // select(v27, 0, v27) + kExprLocalGet, + 2, + kExprI32Const, + 0, + kExprLocalGet, + 2, + kExprSelect, + + // If block to split the block (pushes Return use to next block) + kExprLocalGet, + 0, + kExprIf, + kWasmVoid, + kExprI32Const, + 0, + kExprGlobalSet, + g.index, + kExprEnd, + + // Return v6, v2 + kExprLocalGet, + 1, + ]); + +const instance = builder.instantiate() +instance.exports.main(); +%WasmTierUpFunction(instance.exports.main);
Regression Test / PoC
diff --git a/test/mjsunit/regress/wasm/regress-524792614.js b/test/mjsunit/regress/wasm/regress-524792614.js
new file mode 100644
index 0000000..9a90f23
--- /dev/null
+++ b/test/mjsunit/regress/wasm/regress-524792614.js
@@ -0,0 +1,61 @@
+// Copyright 2026 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flags: --allow-natives-syntax
+
+d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
+
+const builder = new WasmModuleBuilder();
+const g = builder.addGlobal(kWasmI32, true);
+
+builder.addFunction('main', makeSig([kWasmI32], [kWasmI32, kWasmI32]))
+ .exportAs('main')
+ .addLocals(kWasmI32, 2)
+ .addBody([
+ // v2 = param 0 + 1
+ kExprLocalGet,
+ 0,
+ kExprI32Const,
+ 1,
+ kExprI32Add,
+ kExprLocalSet,
+ 1,
+
+ // v27 = 0 - v2
+ kExprI32Const,
+ 0,
+ kExprLocalGet,
+ 1,
+ kExprI32Sub,
+ kExprLocalSet,
+ 2,
+
+ // select(v27, 0, v27)
+ kExprLocalGet,
+ 2,
+ kExprI32Const,
+ 0,
+ kExprLocalGet,
+ 2,
+ kExprSelect,
+
+ // If block to split the block (pushes Return use to next block)
+ kExprLocalGet,
+ 0,
+ kExprIf,
+ kWasmVoid,
+ kExprI32Const,
+ 0,
+ kExprGlobalSet,
+ g.index,
+ kExprEnd,
+
+ // Return v6, v2
+ kExprLocalGet,
+ 1,
+ ]);
+
+const instance = builder.instantiate()
+instance.exports.main();
+%WasmTierUpFunction(instance.exports.main);
Original Bug Report
DCHECK failure in NextIntervalStartsInDifferentBlocks(*interval, *next_interval) in register-alloc
Detailed Report: https://clusterfuzz.com/testcase?key=4840004635361280
Fuzzer: None Job Type: linux_asan_d8_v8_arm64_dbg Platform Id: linux
Crash Type: DCHECK failure Crash Address: Crash State: NextIntervalStartsInDifferentBlocks(*interval, *next_interval) in register-alloc v8::internal::compiler::LiveRangeBuilder::Verify auto v8::internal::compiler::turboshaft::Pipeline::Run<v8::internal::compiler::t
Sanitizer: address (ASAN)
Regressed: https://clusterfuzz.com/revisions?job=linux_asan_d8_v8_arm64_dbg&range=99794:99795
Reproducer Testcase: https://clusterfuzz.com/download?testcase_id=4840004635361280
Issue filed automatically.
To reproduce this, please build the target in this report and run it against the reproducer testcase. Please use the GN arguments provided at bottom of this report when building the binary.
If you have trouble reproducing, please also export the environment variables listed under “[Environment]” in the crash stacktrace.
If you have any feedback on reproducing test cases, let us know at https://forms.gle/Yh3qCYFveHj6E5jz5 so we can improve.