CVE-2026-3910
Overview
Background
- Maglev
- V8’s mid-tier optimizing compiler that builds an SSA graph from bytecode to produce faster machine code than the baseline interpreter.
- Phi untagging
- A Maglev pass that rewrites tagged SSA
Phinodes into untagged (raw integer or float) representations to avoid boxing values across control-flow merges. - Tagged value
- A V8 value representation where a machine word encodes either a small integer (
Smi) or a pointer to a heap object, so the runtime can distinguish types. - `ForceHeapObject` / retagging
- The operation that converts an untagged raw value back into a proper tagged heap object when a later use requires the tagged form.
Root Cause Analysis
The vulnerable code path is Maglev’s Phi untagging pass, controlled by the v8_flags.maglev_untagged_phis flag defined in flag-definitions.h, which transforms tagged Phi nodes into untagged representations to elide boxing at control-flow merge points. The correctness invariant this pass must maintain is that whenever an untagged Phi value flows to a use expecting a tagged value, it is faithfully retagged (via ForceHeapObject when a Smi cannot hold it, as the accompanying test regress-490450922-3.js exercises for 0x7fffffff).
The bug is a class of “inappropriate implementation” logic errors in which this untagging/retagging accounting was unsound, so an optimized function could observe or store a mis-typed value (for example an out-of-range integer or a value that was never correctly retagged). Because the accumulated defects in the pass were numerous enough that individual fixes were not keeping up, the fix disables the entire pass by default rather than patching one specific miscompilation. Flipping maglev_untagged_phis to false forces Maglev to keep Phi values tagged, eliminating the unsound representation conversions and the miscompilations that flowed from them.
maglev_untagged_phis by default until the pass can be made correct.Attack Path
- Deliver script The attacker gets a victim to load a page whose JavaScript is designed to trigger Maglev optimization of a carefully shaped function.
- Force optimization
The script warms up a function so V8 promotes it to Maglev, where the buggy Phi untagging pass runs on a
Phiat a control-flow merge (as%OptimizeFunctionOnNextCall(foo)does in the regression test). - Trigger unsound untagging
Inputs are chosen so an untagged
Phicarries a value that is not soundly retagged, causing the optimized code to treat a value as the wrong representation or type. - Confuse types or values The miscompiled function reads or writes a value with an incorrect representation, breaking V8’s type/value invariants inside the optimized code.
Impact Assessment
Files Changed
src/flags/flag-definitions.htest/mjsunit/turbolev/regress-490450922-3.js
Audit Directions
- Optimizer representation changesReview every Maglev/Turbolev pass that converts between tagged and untagged representations and confirm each untagged value is soundly retagged (
ForceHeapObjectwhere aSmicannot hold it) at all uses. - Feature-flag guarded miscompilationsTreat flags like
maglev_untagged_phis,maglev_hoist_osr_value_phi_untagging, and similar compiler toggles as re-enablement risk points and re-audit the underlying pass before any of them is flipped back on. - Merge-point value flowAudit
Phihandling at control-flow merges for values near representation boundaries (e.g.0x7fffffff, out-of-Smi-range integers, and float/int transitions) where type or range assumptions can be silently violated.
Patch
From 7076ba135fab58910afe9bc8a828dfe1e40c4355 Mon Sep 17 00:00:00 2001
From: Darius Mercadier <dmercadier@chromium.org>
Date: Tue, 10 Mar 2026 18:01:59 +0100
Subject: [PATCH] [maglev] disable Phi untagging
There are currently a bit too many issues with Phi untagging; let's
disable it by default until we get things under control.
Bug: 491410818
Change-Id: I98a4884b5374fad9ac98df0ab57c4203a1ce8403
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7653638
Reviewed-by: Michael Lippautz <mlippautz@chromium.org>
Commit-Queue: Michael Lippautz <mlippautz@chromium.org>
Commit-Queue: Darius Mercadier <dmercadier@chromium.org>
Auto-Submit: Darius Mercadier <dmercadier@chromium.org>
Cr-Commit-Position: refs/heads/main@{#105711}
---
diff --git a/src/flags/flag-definitions.h b/src/flags/flag-definitions.h
index 26ce19c..49840a8 100644
--- a/src/flags/flag-definitions.h
+++ b/src/flags/flag-definitions.h
@@ -746,7 +746,7 @@
DEFINE_BOOL(maglev_reuse_stack_slots, true,
"reuse stack slots in the maglev optimizing compiler")
-DEFINE_BOOL(maglev_untagged_phis, true,
+DEFINE_BOOL(maglev_untagged_phis, false,
"enable phi untagging in the maglev optimizing compiler")
DEFINE_BOOL(maglev_hoist_osr_value_phi_untagging, true,
"enable phi untagging to hoist untagging of osr values")
diff --git a/test/mjsunit/turbolev/regress-490450922-3.js b/test/mjsunit/turbolev/regress-490450922-3.js
index 479591f..f688d74 100644
--- a/test/mjsunit/turbolev/regress-490450922-3.js
+++ b/test/mjsunit/turbolev/regress-490450922-3.js
@@ -22,10 +22,14 @@
%OptimizeFunctionOnNextCall(foo);
foo(true);
assertEquals(0x7fffffff, o.x);
-assertOptimized(foo);
+// TODO(dmercadier): re-enable this assertOptimized once Phi untagging has been
+// re-enabled.
+// assertOptimized(foo);
// Even with a Smi it should be fine because we should ForceHeapObject when
// retagging.
foo(false);
assertEquals(42, o.x);
-assertOptimized(foo);
+// TODO(dmercadier): re-enable this assertOptimized once Phi untagging has been
+// re-enabled.
+//assertOptimized(foo);
Regression Test / PoC
diff --git a/test/mjsunit/turbolev/regress-490450922-3.js b/test/mjsunit/turbolev/regress-490450922-3.js index 479591f..f688d74 100644 --- a/test/mjsunit/turbolev/regress-490450922-3.js +++ b/test/mjsunit/turbolev/regress-490450922-3.js @@ -22,10 +22,14 @@ %OptimizeFunctionOnNextCall(foo); foo(true); assertEquals(0x7fffffff, o.x); -assertOptimized(foo); +// TODO(dmercadier): re-enable this assertOptimized once Phi untagging has been +// re-enabled. +// assertOptimized(foo); // Even with a Smi it should be fine because we should ForceHeapObject when // retagging. foo(false); assertEquals(42, o.x); -assertOptimized(foo); +// TODO(dmercadier): re-enable this assertOptimized once Phi untagging has been +// re-enabled. +//assertOptimized(foo);