CVE-2026-5871
Overview
Background
- Maglev
- V8’s mid-tier optimizing JIT that builds a graph of
ValueNodes and tracks a staticNodeTypefor each value to elide redundant checks. - `NodeType`
- A lattice of value-shape facts (
kSmi,kHeapNumber,kAnyHeapObject, etc.);IntersectType/UnionTypenarrow or widen a node’s tracked type, and consumers rely on it as ground truth. - `Phi`
- A graph node merging values from multiple control-flow predecessors; maglev’s phi untagging can pick an unboxed-double representation, converting a
Smiinput into aHeapNumberat runtime. - `BuildCheckHeapObject`
- Emits a
CheckHeapObjectdeopt guard and narrows the value’s trackedNodeTypetokAnyHeapObject, removingkSmi.
Root Cause Analysis
Verify before building a PoC
What the commit does not establish: Type-narrowing mechanism matches diff, but the specific downstream consumer reading a HeapNumber's double payload as an object pointer, and resulting read/write primitives, are not shown by the diff and are stated as fact. Patch comment calls fix a partial band-aid.
BuildCheckHeapObject proves a value is not a Smi by emitting a CheckHeapObject guard, then calls EnsureType(object, NodeType::kAnyHeapObject) to strip kSmi from the node’s tracked type. For an ordinary node this is sound, but a Phi whose input set includes a Smi can be later rewritten by maglev’s phi untagging to carry an unboxed double, materializing a HeapNumber where a Smi used to flow. Because kAnyHeapObject was intersected in without re-admitting kHeapNumber, the recorded type became strictly narrower than the value’s real runtime range, excluding the very HeapNumber that untagging can produce. A downstream consumer trusting that too-narrow type treated the value as a non-HeapNumber heap object and interpreted the HeapNumber’s raw double payload as an object pointer.
The fix detects the vulnerable shape — object->Is<Phi>() with an initial type that NodeTypeCanBe(initial_type, NodeType::kSmi) — and calls UnionType(NodeType::kHeapNumber) back into the phi’s NodeInfo so the widened possibility survives the narrowing.
Smi phi input into a HeapNumber, so narrowing a phi’s type to kAnyHeapObject while dropping kHeapNumber records a type the value can violate at runtime.Attack Path
- Craft a polymorphic phi
Author JS whose control flow merges a
Smi-producing branch and a heap-object branch into onePhi. - Force the heap-object check
Use the merged value in a context that triggers
BuildCheckHeapObject, strippingkSmidown tokAnyHeapObject. - Trigger phi untagging
Feed the phi through arithmetic that makes maglev untag it onto the unboxed-double path, so the
Smiinput becomes aHeapNumberat runtime. - Reach the miscompiled consumer
Route the phi to a downstream node that, trusting the
HeapNumber-excluding type, reads the value as a tagged object pointer. - Corrupt memory Supply a double whose bit pattern forms an attacker-chosen pointer, achieving type confusion and controlled memory corruption in the renderer.
Impact Assessment
TypeConfusion in V8’s maglev compiler exploitable from a single untrusted web page with no special permissions. Misinterpreting a HeapNumber payload as an object pointer yields attacker-influenced read/write primitives, leading to renderer-process memory corruption and likely remote code execution within the sandbox.Files Changed
src/maglev/maglev-graph-builder.cc
Audit Directions
- Type narrowing that ignores later rewritesReview every
IntersectType/EnsureTypecall that removeskSmiorkHeapNumberfrom aPhi, and verify it accounts for representation-changing passes like phi untagging that can re-introduce the removed shape. - Check-then-record asymmetryAudit sites where a runtime guard (
CheckHeapObject,CheckSmi, map checks) drives a staticNodeTypeupdate, ensuring the recorded type is not narrower than what subsequent optimizations can produce. - Consumers trusting `kAnyHeapObject`Inspect nodes that dereference values typed
kAnyHeapObjectwithout excludingkHeapNumber, since a stale narrow type there converts a benign double into a fake pointer.
Patch
From c1e78b455e3224e286914b5ac241fbea064cb51d Mon Sep 17 00:00:00 2001 From: Darius Mercadier <[email protected]> Date: Thu, 26 Mar 2026 10:36:49 +0100 Subject: [PATCH] [maglev] Account for phi smi type widening in BuildCheckHeapObject Bug: 495679730 Change-Id: Icecb844aac559f2de15ac4ec3065ab1df6fb0585 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7701796 Reviewed-by: Leszek Swirski <[email protected]> Commit-Queue: Leszek Swirski <[email protected]> Auto-Submit: Darius Mercadier <[email protected]> Cr-Commit-Position: refs/heads/main@{#106058} --- diff --git a/src/maglev/maglev-graph-builder.cc b/src/maglev/maglev-graph-builder.cc index d7a31c2..5bff3e6 100644 --- a/src/maglev/maglev-graph-builder.cc +++ b/src/maglev/maglev-graph-builder.cc @@ -4196,8 +4196,8 @@ ReduceResult MaglevGraphBuilder::BuildCheckHeapObject(ValueNode* object) { // Check for the empty type first so that we catch the case where // GetType(object) is already empty. - if (IsEmptyNodeType( - IntersectType(GetType(object), NodeType::kAnyHeapObject))) { + NodeType initial_type = GetType(object); + if (IsEmptyNodeType(IntersectType(initial_type, NodeType::kAnyHeapObject))) { return EmitUnconditionalDeopt(DeoptimizeReason::kSmi); } if (Phi* phi = object->TryCast<Phi>()) { @@ -4208,6 +4208,17 @@ phi->SetUseRequiresHeapObject(); } if (EnsureType(object, NodeType::kAnyHeapObject)) return ReduceResult::Done(); + if (object->Is<Phi>() && NodeTypeCanBe(initial_type, NodeType::kSmi)) { + // If {initial_type} contains kSmi, then phi untagging could widen this to a + // HeapNumber. Since the `EnsureType(.. kAnyHeapObject)` above just removed + // `kSmi` from the type, we need to make sure that still don't forget that + // HeapNumber is actually still a possibility. + // TODO(dmercadier): this is only a small band-aid: actually, any + // GetType(phi) could return Smi when the actual type ends up being + // HeapNumber. + NodeInfo* info = GetOrCreateInfoFor(object); + info->UnionType(NodeType::kHeapNumber); + } return AddNewNode<CheckHeapObject>({object}); }
Original Bug Report
Phi untagging invalidates BuildCheckHeapObject's type refinement
Splitting from https://issues.chromium.org/u/1/issues/495149864#comment17 because this might be a separate bug.
Repro:
let o = { x : "abc" };
o.x = []; // Making non-string, non-const, but still HeapObject
function foo(c1, c2) {
let phi1 = c1 ? 42 : 0x40000000;
phi1 + 1; // inserting smi check
let phi2 = c2 ? true : phi1;
o.x = phi2;
}
%PrepareFunctionForOptimization(foo);
foo(true, true);
foo(true, true);
%OptimizeMaglevOnNextCall(foo);
foo(true, true);
foo(true, true);
foo(false, false);
with flags –allow-natives-syntax –maglev-untagged-phis –maglev-assert-types
Failure:
CheckMaglevType failed
DebugPrint: 0xf9e0104b685: [HeapNumber]
- map: 0x0f9e00000515 <Map[12](HEAP_NUMBER_TYPE)>
- value: 1073741824.0
0xf9e00000515: [Map] in ReadOnlySpace
- map: 0x0f9e00000475 <MetaMap (0x0f9e0000002d <null>)>
- type: HEAP_NUMBER_TYPE
- instance size: 12
- elements kind: HOLEY_ELEMENTS
- enum length: invalid
- stable_map
- non-extensible
- back pointer: 0x0f9e00000011 <undefined>
- prototype_validity_cell: 0
- instance descriptors (own) #0: 0x0f9e0000080d <DescriptorArray[0]>
- prototype: 0x0f9e0000002d <null>
- constructor: 0x0f9e0000002d <null>
- dependent code: 0x0f9e000007f5 <Other heap object (WEAK_ARRAY_LIST_TYPE)>
- construction counter: 0
Expected type: : DebugPrint: Smi: 0x10 (16)
Actual type: : DebugPrint: Smi: 0x2 (2)