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 (plausible) — 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 AIPlausible
Reviewer notes: 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.
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker495679730
Fix commitc1e78b455e32 (v8/v8) +13/-2
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-24 (per vendor advisory)
Fix landed2026-03-26 (public git)
Disclosed2026-04-07
Patch gap12 days — fix public in source before the advisory

Background

Maglev
V8’s mid-tier optimizing JIT that builds a graph of ValueNodes and tracks a static NodeType for each value to elide redundant checks.
`NodeType`
A lattice of value-shape facts (kSmi, kHeapNumber, kAnyHeapObject, etc.); IntersectType/UnionType narrow 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 Smi input into a HeapNumber at runtime.
`BuildCheckHeapObject`
Emits a CheckHeapObject deopt guard and narrows the value’s tracked NodeType to kAnyHeapObject, removing kSmi.

Root Cause Analysis

Verify before building a PoC

This analysis is consistent with the fixing commit but not fully provable from it: part of the vulnerable path lives in code the fix commit does not touch. Confirm the trigger and the sink against the source before relying on it for exploit development.
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.

Key insight
Phi untagging can retroactively widen a 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

  1. Craft a polymorphic phi Author JS whose control flow merges a Smi-producing branch and a heap-object branch into one Phi.
  2. Force the heap-object check Use the merged value in a context that triggers BuildCheckHeapObject, stripping kSmi down to kAnyHeapObject.
  3. Trigger phi untagging Feed the phi through arithmetic that makes maglev untag it onto the unboxed-double path, so the Smi input becomes a HeapNumber at runtime.
  4. 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.
  5. 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

This is a 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 rewrites
    Review every IntersectType/EnsureType call that removes kSmi or kHeapNumber from a Phi, and verify it accounts for representation-changing passes like phi untagging that can re-introduce the removed shape.
  • Check-then-record asymmetry
    Audit sites where a runtime guard (CheckHeapObject, CheckSmi, map checks) drives a static NodeType update, ensuring the recorded type is not narrower than what subsequent optimizations can produce.
  • Consumers trusting `kAnyHeapObject`
    Inspect nodes that dereference values typed kAnyHeapObject without excluding kHeapNumber, since a stale narrow type there converts a benign double into a fake pointer.
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});
 }
 
Loading diff…

Original Bug Report

reported by [email protected]

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)
View on issue tracker