High watch CVSS 6.5 chrome Uninitialized Memory 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
6.5
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUninitialized Use in V8
DescriptionUninitialized Use in V8
ComponentV8
Sub-componentCompiler
Bug ClassUninitialized Memory
Action tierwatch — information disclosure — security-relevant, below the RCE-class bar
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker524792614
Fix commit78afe7b553f2 (v8/v8) +73/-6
CWECWE-457 (Use of uninitialized)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-06-17 (per vendor advisory)
Fix landed2026-07-07 (public git)
Disclosed2026-07-14
Patch gap7 days — fix public in source before the advisory

Background

TurboFan instruction selector
the V8 backend pass that lowers scheduled IR nodes into machine instructions, with per-architecture files instruction-selector-arm.cc and instruction-selector-arm64.cc.
Flag-setting binop
an arithmetic operation (e.g. kExprI32Add, kExprI32Sub) whose result also sets the condition flags, letting a following compare be folded away via MapForFlagSettingBinop.
`FlagsContinuation` (`cont`)
the object describing how flag results are consumed, which may be a branch (cont->IsBranch()), a set (cont->IsSet()), a deoptimize, or a trap.
`IsOnlyUserOfNodeInSameBlock`
a selector query returning whether the comparison is the sole in-block user of the binop *node, used to justify folding the binop into the continuation.

Root Cause Analysis

When lowering a comparison against zero, the selector tried to fold a flag-setting binop directly into the FlagsContinuation whenever IsOnlyUserOfNodeInSameBlock(*node, binop) held, without checking what kind of continuation was in play. For continuations that are not a branch or a set (for example a trap or deoptimize), the selector may have already emitted instructions after exploring through the original conditional-setting instruction, so tying the binop to the continuation forces the flag-setting instruction to be pulled past an existing use. This breaks the emission schedule: the register that should hold the binop result is read before the flag-setting instruction that defines it has run, so a garbage/uninitialized register value is consumed.

The fix gates the fold with (cont->IsBranch() || cont->IsSet()), so the optimization only applies to continuation kinds that cannot reorder the flag-setting instruction past its users, preserving the invariant that a value is defined before it is used.

Key insight
The single mistake is applying the “only in-block user” fold unconditionally, ignoring the continuation kind; the fix restricts it to IsBranch()/IsSet() continuations, the only ones where moving the flag-setting instruction cannot cross a prior use and corrupt the schedule.

Attack Path

  1. Craft a WebAssembly module Build a function whose body computes v2 = param + 1 and v27 = 0 - v2, both flag-setting binops feeding later uses, as in regress-524792614.js.
  2. Force block splitting Insert an kExprIf block so a Return use of the binop is pushed into a following basic block, creating cross-block users while the compare stays in-block.
  3. Trigger optimizing compilation Instantiate and call main, then invoke %WasmTierUpFunction to run TurboFan and reach the vulnerable selector path on arm/arm64.
  4. Mis-fold the binop The selector folds the flag-setting binop into a non-branch/non-set continuation, scheduling the flag-setting instruction after a use of its result.
  5. Consume uninitialized register The generated code reads the target register before it is defined, yielding uninitialized memory used as a program value.

Impact Assessment

An attacker who can run crafted WebAssembly (reachable from any web page in the renderer via JIT) obtains use of an uninitialized register value in optimized code, giving an information-leak or memory-corruption primitive inside the sandboxed V8/renderer process. Preconditions are arm or arm64 targets and tier-up to TurboFan; no elevated privileges are required beyond executing script.

Files Changed

  • src/compiler/backend/arm/instruction-selector-arm.cc
  • src/compiler/backend/arm64/instruction-selector-arm64.cc
  • test/mjsunit/regress/wasm/regress-524792614.js

Audit Directions

  • Continuation-kind assumptions
    Review other FlagsContinuation folds that assume any continuation is safe; each should verify IsBranch()/IsSet() before moving a flag-setting instruction.
  • In-block-user rewrites
    Audit uses of IsOnlyUserOfNodeInSameBlock (and similar single-user checks) that reorder defining instructions, ensuring the move cannot cross an existing use.
  • Per-architecture parity
    When one instruction selector is patched, check the sibling backends (arm, arm64, x64, ia32, etc.) for the same unguarded pattern, as this fix spanned both arm files.
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);
Loading diff…

Regression Test / PoC

shipped with the fix
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);
Loading diff…

Original Bug Report

reported by [email protected]

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.

View on issue tracker