Overview

Medium
Severity
6.5
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactType Confusion in V8
DescriptionType Confusion in V8
ComponentV8
Sub-componentMaglev
Bug ClassType Confusion
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable
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.
Tracker490353576
Fix commit54fec2bb0639 (v8/v8) +28/-3
CWECWE-843 (Type confusion)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-03-06 (per vendor advisory)
Fix landed2026-03-19 (public git)
Disclosed2026-05-12
Patch gap54 days — fix public in source before the advisory

Background

Maglev
V8’s mid-tier optimizing JIT that builds an SSA-style graph and, under --maglev-untagged-phis, may represent a Phi in a raw untagged (e.g. Int32) form instead of a boxed tagged value.
Static type vs. KNA type
unmerged_phi->type() is a node’s conservative static NodeType, whereas the known-node-aspects (KNA) type from unmerged_aspects.GetTypeUnchecked is the refined type learned along a control path, for example once a CheckedSmiUntag has proven a value is a Smi.
`SetUseRequiresSmi`
a flag on an input to a loop Phi that records the backedge value is used as a Smi, forcing later type/untagging decisions to stay consistent with that constraint.
`Smi` untagging
replacing a tagged small-integer Phi with a raw machine integer, which is only sound if every value flowing in is truly a Smi.

Root Cause Analysis

When merging a loop backedge in src/maglev/maglev-interpreter-frame-state.cc, a prior fix (crrev.com/c/7633545) tried to call SetUseRequiresSmi whenever the backedge of a Phi had Smi type, but it queried unmerged_phi->type(), the node’s static type, rather than its KNA type. For non-loop phis the static type is typically NodeType::kAny, while the KNA type only becomes NodeType::kSmi once a CheckedSmiUntag is seen on the path, so the NodeTypeIs(unmerged_phi->type(), NodeType::kSmi) guard failed to fire and SetUseRequiresSmi was never called. Missing that flag lets Maglev untag the Phi as a Smi based on a CheckedSmiUntag, then later invalidate the very type assumption that justified the untagging without re-checking it, leaving an untagged Phi that can carry a non-Smi value.

The fix computes unmerged_type from unmerged_aspects.GetTypeUnchecked(builder->broker(), unmerged) (the KNA type) and tests NodeTypeIs(unmerged_type, NodeType::kSmi), so the flag is set whenever the path-refined type is Smi; a DCHECK also asserts the static type is not kSmi in the fallthrough, since that case would have been caught above.

Key insight
The single mistake was deciding SetUseRequiresSmi from the backedge Phi’s static type (unmerged_phi->type()) instead of its path-sensitive KNA type; the fix reads the KNA type via unmerged_aspects.GetTypeUnchecked and passes it to NodeTypeIs, so Smi-ness introduced by a CheckedSmiUntag is no longer missed.

Attack Path

  1. Craft a loop with a conditionally-changing phi Author a hot loop where a variable (x) starts as a Smi but is reassigned to a large Int32-but-non-Smi value (0x4e000000) partway through iteration, feeding a backedge Phi.
  2. Trigger a CheckedSmiUntag on the phi Use the value in an operation that inserts a CheckedSmiUntag (e.g. x | 0 and indexing like delete ArrayBuffer[x]), which populates the phi’s KNA type as NodeType::kSmi even though its static type stays NodeType::kAny.
  3. Bypass the Smi-use flag Because the buggy check consulted unmerged_phi->type(), the Smi KNA type is not detected, so SetUseRequiresSmi is skipped and Maglev leaves the untagged Phi without the constraint that would keep its Smi assumption honest.
  4. Force unsound untagging Maglev untags the Phi to a raw integer under --maglev-untagged-phis, but the invariant that all backedge values are Smi is silently violated when the non-Smi value flows in.
  5. Achieve type confusion The untagged Phi is subsequently treated as a Smi/tagged value, confusing a raw integer with a tagged pointer and yielding type confusion and potential out-of-bounds access.

Impact Assessment

An attacker who can run JavaScript in the renderer gains a type confusion in V8’s Maglev-compiled code, where a Phi untagged as a Smi can hold a non-Smi value and be misinterpreted as tagged, enabling memory corruption / out-of-bounds access. Exploitation runs in the renderer process and requires only that the victim execute attacker-controlled script that gets tiered up to Maglev with --maglev-untagged-phis behavior, using a loop shaped to introduce a CheckedSmiUntag-derived Smi KNA type on a backedge Phi.

Changed Functions

FunctionChangeNotes
for
test/mjsunit/maglev/regress-490353576.js
modified
if
test/mjsunit/maglev/regress-490353576.js
modified

Files Changed

  • src/maglev/maglev-interpreter-frame-state.cc
  • test/mjsunit/maglev/regress-490353576.js

Audit Directions

  • Static type vs. KNA type at merges
    Review every Maglev decision made at phi/backedge merges (untagging, SetUseRequiresSmi, SetUseRequiresHeapObject) that reads a node’s static type() where the path-refined KNA type from GetTypeUnchecked is the correct source.
  • Loop-phi soundness constraints
    Audit places where a type refinement (e.g. from CheckedSmiUntag) can later be invalidated, ensuring a corresponding use-constraint flag is recorded so representation choices stay sound across the backedge.
  • Non-loop vs. loop phi assumptions
    Look for code that assumes static and refined types coincide “more or less” for phis, which holds for loop phis but not non-loop phis whose static type is typically kAny.
From 54fec2bb063997d5f545c90b8b342b5d620c994f Mon Sep 17 00:00:00 2001
From: Darius Mercadier <[email protected]>
Date: Tue, 17 Mar 2026 11:20:30 +0100
Subject: [PATCH] [maglev] Fix backedge Smi type recording

https://crrev.com/c/7633545 tried to make sure that we call
SetUseRequiresSmi when the backedge of a Phi has Smi type, but it was
using the static type of the backedge rather than its KNA type. For
loop phis, it's more or less the same, but for non-loop phis, the
static type is typically Any, whereas the KNA type is populated as
soon as a CheckedSmiUntag is encountered (as is the case in the
repro).

Fixed: 490353576
Change-Id: I4ed65d4f3791c3c9b4307cf9997c2367f8dfbf56
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7665886
Auto-Submit: Darius Mercadier <[email protected]>
Reviewed-by: Leszek Swirski <[email protected]>
Commit-Queue: Darius Mercadier <[email protected]>
Cr-Commit-Position: refs/heads/main@{#105897}
---

diff --git a/src/maglev/maglev-interpreter-frame-state.cc b/src/maglev/maglev-interpreter-frame-state.cc
index 595d75c..2d3a2f9 100644
--- a/src/maglev/maglev-interpreter-frame-state.cc
+++ b/src/maglev/maglev-interpreter-frame-state.cc
@@ -1000,13 +1000,13 @@
     return;
   }
   DCHECK_EQ(result->owner(), owner);
-  NodeType type =
+  NodeType unmerged_type =
       unmerged_aspects.GetTypeUnchecked(builder->broker(), unmerged);
   unmerged = EnsureTagged(builder, unmerged_aspects, unmerged,
                           predecessors_[predecessors_so_far_]);
   result->set_input(predecessor_count_ - 1, unmerged);
 
-  result->merge_post_loop_type(type);
+  result->merge_post_loop_type(unmerged_type);
   // We've just merged the backedge, which means that future uses of this Phi
   // will be after the loop, so we can now promote `post_loop_type` to the
   // regular `type`.
@@ -1021,7 +1021,7 @@
     // sminess.
     if (result->uses_require_smi()) {
       unmerged_phi->SetUseRequiresSmi();
-    } else if (NodeTypeIs(unmerged_phi->type(), NodeType::kSmi)) {
+    } else if (NodeTypeIs(unmerged_type, NodeType::kSmi)) {
       // The backedge has Smi type, but it's possible that this is only true
       // because {result} itself is eventually known to be a Smi, thanks to for
       // instance a CheckedSmiUntag. If we don't set {use_requires_smi}, then
@@ -1029,6 +1029,10 @@
       // invalidating the type of the backedge, and in turn potentially
       // invalidating some Phi untagging without us realizing it.
       unmerged_phi->SetUseRequiresSmi();
+    } else {
+      // If the static type of {unmerged_phi} was kSmi, we should have gone into
+      // the branch above and called SetUseRequiresSmi.
+      DCHECK(!NodeTypeIs(unmerged_phi->type(), NodeType::kSmi));
     }
     if (result->uses_require_heap_object()) {
       unmerged_phi->SetUseRequiresHeapObject();
diff --git a/test/mjsunit/maglev/regress-490353576.js b/test/mjsunit/maglev/regress-490353576.js
new file mode 100644
index 0000000..c912a2c
--- /dev/null
+++ b/test/mjsunit/maglev/regress-490353576.js
@@ -0,0 +1,21 @@
+// 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: --no-concurrent-recompilation --maglev-untagged-phis
+
+function foo() {
+  let x = 0;
+  var y = 0x4e000000;
+  for (let i = 0; i < 250; i++) {
+    if (i == 100) {
+      x = y;
+    }
+    delete ArrayBuffer[x];
+    x | 0;
+  }
+
+  return x;
+}
+
+assertEquals(1308622848, foo());
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/test/mjsunit/maglev/regress-490353576.js b/test/mjsunit/maglev/regress-490353576.js
new file mode 100644
index 0000000..c912a2c
--- /dev/null
+++ b/test/mjsunit/maglev/regress-490353576.js
@@ -0,0 +1,21 @@
+// 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: --no-concurrent-recompilation --maglev-untagged-phis
+
+function foo() {
+  let x = 0;
+  var y = 0x4e000000;
+  for (let i = 0; i < 250; i++) {
+    if (i == 100) {
+      x = y;
+    }
+    delete ArrayBuffer[x];
+    x | 0;
+  }
+
+  return x;
+}
+
+assertEquals(1308622848, foo());
Loading diff…

Original Bug Report

reported by [email protected]

V8 correctness failure in sources: d3

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

Fuzzer: foozzie_js_fuzzer Job Type: v8_foozzie_v2 Platform Id: linux

Crash Type: V8 correctness failure Crash Address: Crash State: sources: d3

Sanitizer: address (ASAN)

Regressed: https://clusterfuzz.com/revisions?job=v8_foozzie_v2&range=105097:105098

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

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