CVE-2026-8540
Overview
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
ScopeInfodescribing 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.
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
- 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. - Warm up and trigger OSR
Prime the function with
%PrepareFunctionForOptimizationand repeated calls, then call it so the loop runs long enough for%OptimizeOsr()to request on-stack replacement at the loop header. - Mis-reconstruct scope during prewalk
Because the OSR loop header has no
saved_statesentry,OsrPrewalkleaves the prior (wrong) context scope info in place instead of adopting the loop header merge point’scontext_scope_info(), so Maglev builds the OSR graph against a mismatched context layout. - Emit type-confused context accesses
Variable loads/stores compiled under the stale
ScopeInforesolve to the wrong context slot or object shape, producing a type confusion between the assumed and actual value/context types. - 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
Changed Functions
| Function | Change | Notes |
|---|---|---|
fortest/mjsunit/maglev/regress-496627235.js |
modified |
Files Changed
src/maglev/maglev-graph-builder.cctest/mjsunit/maglev/regress-496627235.js
Audit Directions
- Scope-info restoration at merge pointsReview every path that sets or omits
SetCurrentScopeInfo/context scope info during prewalk and graph building for offsets lacking asaved_statesentry, since a missing restore silently keeps stale assumptions. - OSR-specific state reconstructionAudit 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 loopsLook 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.
Patch
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));
Regression Test / PoC
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));
Original Bug Report
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.