Overview

High
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactType Confusion in V8
DescriptionType Confusion in V8
ComponentV8
Sub-componentMaglev
Bug ClassType Confusion
Action tierpatch-now — corruption primitive, web-reachable in default config (confirmed) — upheld by independent review by AI
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.
Independent review by AIConfirmed
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker496627235
Fix commite6c5b6cb3a65 (v8/v8) +27/-0
CWECWE-843 (Type confusion)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-03-26 (per vendor advisory)
Fix landed2026-03-27 (public git)
Disclosed2026-05-12
Patch gap46 days — fix public in source before the advisory

Background

Maglev
V8’s mid-tier optimizing compiler that builds a graph from bytecode and relies on accurate compile-time type and scope assumptions.
OSR (on-stack replacement)
a mechanism that swaps a still-running unoptimized function (typically stuck in a hot loop) for optimized code mid-execution, entering the optimized frame at the loop header.
`OsrPrewalk`
the Maglev pass that walks the bytecode ahead of graph building for an OSR compilation to reconstruct the state that must hold at the OSR entry point, including the active context scope info.
Context scope info
the ScopeInfo describing the layout and kind of the currently active context, which Maglev uses to type context accesses and decide how variable loads/stores are compiled.

Root Cause Analysis

During an OSR compilation, OsrPrewalk replays the bytecode to re-establish the compiler’s state at each offset before graph building, and at merge points it must restore the context scope info that was in effect when control flow joins. The vulnerable code only restored scope info from saved_states (SetCurrentScopeInfo(it->second)) and, when no explicit saved state existed for the offset, silently left the current scope info unchanged. This violated the invariant that the scope info at an OSR merge point must match the actual context established along the paths reaching that merge; when a merge state carried its own context_scope_info() (e.g. a loop header reached after a context-changing region such as a try/catch that materializes a closure), the prewalk continued with stale/incorrect scope info.

The fix restores the correct value by falling back to the mergepoint’s own recorded scope info — SetCurrentScopeInfo(merge_states_[offset]->context_scope_info()) when merge_states_[offset] is non-null and has_context_scope_info() — so subsequent context and variable accesses are typed against the real context layout.

Key insight
The single mistake is that OsrPrewalk restored scope info only from saved_states and never from the merge point itself, so at an OSR loop header the compiler kept a stale ScopeInfo; the fix adds the missing fallback to merge_states_[offset]->context_scope_info(), re-synchronizing compile-time scope assumptions with the actual runtime context.

Attack Path

  1. Create a context-shifting prologue Define a function whose body first runs a region that materializes a distinct context (a try { let x; ()=>x; throw ... } catch(e){} block capturing a variable), then enters a hot loop that reads an outer-scope variable.
  2. Warm up and trigger OSR Prime the function with %PrepareFunctionForOptimization and repeated calls, then call it so the loop runs long enough for %OptimizeOsr() to request on-stack replacement at the loop header.
  3. Mis-reconstruct scope during prewalk Because the OSR loop header has no saved_states entry, OsrPrewalk leaves the prior (wrong) context scope info in place instead of adopting the loop header merge point’s context_scope_info(), so Maglev builds the OSR graph against a mismatched context layout.
  4. Emit type-confused context accesses Variable loads/stores compiled under the stale ScopeInfo resolve to the wrong context slot or object shape, producing a type confusion between the assumed and actual value/context types.
  5. Leverage the confusion The attacker uses the mistyped access to read or write memory as the wrong type, a primitive that can be escalated toward arbitrary read/write inside the renderer.

Impact Assessment

An attacker who runs crafted JavaScript in the renderer gains a type confusion in V8’s Maglev-compiled code, letting a value or context slot be accessed as the wrong type and yielding a memory corruption primitive. It executes in the sandboxed renderer process and requires only that the victim visit an attacker-controlled page; the preconditions are that the function be tiered up via OSR after passing through a context-changing region, which page script fully controls.

Changed Functions

FunctionChangeNotes
for
test/mjsunit/maglev/regress-496627235.js
modified

Files Changed

  • src/maglev/maglev-graph-builder.cc
  • test/mjsunit/maglev/regress-496627235.js

Audit Directions

  • Scope-info restoration at merge points
    Review every path that sets or omits SetCurrentScopeInfo/context scope info during prewalk and graph building for offsets lacking a saved_states entry, since a missing restore silently keeps stale assumptions.
  • OSR-specific state reconstruction
    Audit OSR entry and prewalk code where compile-time state is rebuilt from bytecode rather than inherited, because divergence from the real runtime context is a recurring type-confusion source.
  • Context-changing regions before loops
    Look for cases where try/catch, closures capturing variables, or other context-materializing constructs precede a loop header that later becomes an OSR target, as these create merge points whose scope info differs from the fall-through path.
From e6c5b6cb3a656b95eb8849d2c274f92bfd29ef32 Mon Sep 17 00:00:00 2001
From: Toon Verwaest <verwaest@chromium.org>
Date: Fri, 27 Mar 2026 17:20:06 +0100
Subject: [PATCH] [maglev] Restore mergepoint scope info in OsrPrewalk

Bug: 496627235
Change-Id: I69bdb19b37e0f56c3094112123fca75dd8d4b01d
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7705960
Commit-Queue: Toon Verwaest <verwaest@chromium.org>
Reviewed-by: Leszek Swirski <leszeks@chromium.org>
Auto-Submit: Toon Verwaest <verwaest@chromium.org>
Cr-Commit-Position: refs/heads/main@{#106118}
---

diff --git a/src/maglev/maglev-graph-builder.cc b/src/maglev/maglev-graph-builder.cc
index 2e874f3..524a78c 100644
--- a/src/maglev/maglev-graph-builder.cc
+++ b/src/maglev/maglev-graph-builder.cc
@@ -17046,6 +17046,9 @@
     auto it = saved_states.find(offset);
     if (it != saved_states.end()) {
       SetCurrentScopeInfo(it->second);
+    } else if (merge_states_[offset] != nullptr &&
+               merge_states_[offset]->has_context_scope_info()) {
+      SetCurrentScopeInfo(merge_states_[offset]->context_scope_info());
     }
 
     PrewalkBytecode();
diff --git a/test/mjsunit/maglev/regress-496627235.js b/test/mjsunit/maglev/regress-496627235.js
new file mode 100644
index 0000000..9236a7c
--- /dev/null
+++ b/test/mjsunit/maglev/regress-496627235.js
@@ -0,0 +1,24 @@
+// 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
+
+let x = 100;
+function f(osr) {
+  try { let x; ()=>x; throw "bla" } catch(e) {};
+  let y;
+  for (let i = 0; i < 10; i++) {
+    if (osr) %OptimizeOsr();
+    osr = false;
+    y = x;
+  }
+  return y;
+}
+
+%PrepareFunctionForOptimization(f);
+f(false);
+f(false);
+f(false);
+f(false);
+assertEquals(100, f(true));
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/test/mjsunit/maglev/regress-496627235.js b/test/mjsunit/maglev/regress-496627235.js
new file mode 100644
index 0000000..9236a7c
--- /dev/null
+++ b/test/mjsunit/maglev/regress-496627235.js
@@ -0,0 +1,24 @@
+// 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
+
+let x = 100;
+function f(osr) {
+  try { let x; ()=>x; throw "bla" } catch(e) {};
+  let y;
+  for (let i = 0; i < 10; i++) {
+    if (osr) %OptimizeOsr();
+    osr = false;
+    y = x;
+  }
+  return y;
+}
+
+%PrepareFunctionForOptimization(f);
+f(false);
+f(false);
+f(false);
+f(false);
+assertEquals(100, f(true));
Loading diff…

Original Bug Report

reported by 24...@project.gserviceaccount.com

DCHECK failure in Holder<To> v8::internal::TrustedCast(Holder<From>, SourceLocation) [To = v8::int

Detailed Report: https://clusterfuzz.com/testcase?key=5559284939128832

Fuzzer: None Job Type: linux_asan_d8_dbg Platform Id: linux

Crash Type: DCHECK failure Crash Address: Crash State: Holder<To> v8::internal::TrustedCast(Holder<From>, SourceLocation) [To = v8::int _ZN2v88internal6Object16ConvertToNumericINS0_6HandleEQsr3stdE16is_convertible_vI Tv8::internal::Object::MaybeType v8::internal::Object::ToNumeric<v8::internal:

Sanitizer: address (ASAN)

Regressed: https://clusterfuzz.com/revisions?job=linux_asan_d8_dbg&range=105997:105998

Reproducer Testcase: https://clusterfuzz.com/download?testcase_id=5559284939128832

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