Overview

High
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in V8
DescriptionInappropriate implementation in V8
ComponentV8
Sub-componentMaglev
Bug ClassLogic Error
Action tierpatch-now — corruption primitive, web-reachable in default config (confirmed) — 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 AIConfirmed
Reviewer notes: Mechanism matches commit message. Minor: HeapObject-in-Smi-field type confusion (stale field representation, possible elided write barrier) is plausible corruption but escalation is inferred; repro needs a contrived secondary Phi, so trigger is uncommon though default-config Maglev.
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker490558172
Fix commit0fff08a529f4 (v8/v8) +82/-1
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-07 (per vendor advisory)
Fix landed2026-03-11 (public git)
Disclosed2026-03-18
Patch gap7 days — fix public in source before the advisory

Background

Maglev
V8’s mid-tier optimizing JIT that builds an SSA graph with Phi nodes and tracks per-node type/representation knowledge (KNA, “known node aspects”).
`Phi` representation & materialization
a Phi can be kept as an untagged Int32, a Smi, or a tagged HeapObject/HeapNumber; when it must become a tagged value it is materialized, and the choice of Smi vs HeapObject must match how consumers store it.
`CheckValueEqualsInt32`
a guard that, when applied to a Phi, records a constant Int32 alternative for that Phi (the value is known to equal a specific in-range integer).
`BuildCheckSmi` / `RecordSmiUse` / `CheckSmi`
BuildCheckSmi ensures a value is a Smi; RecordSmiUse marks that a use requires the value to be materialized as a Smi; CheckSmi is the runtime guard, which BuildCheckSmi may elide for an in-range constant.

Root Cause Analysis

When a Phi first flows through CheckValueEqualsInt32, Maglev records an in-range constant Int32 alternative for it. If that Phi is later fed to BuildCheckSmi, the pre-patch code reached if (std::optional<int32_t> constant_value = TryGetInt32Constant(object)) { if (Smi::IsValid(...)) return object; } and returned early — skipping the switch where RecordSmiUse(object) was called only in the ValueRepresentation::kTagged case. As a result the KNA learned the Phi “is a Smi,” but the materialization machinery was never told that a use requires Smi form, so UseRequiresSmi was not honored. If the same Phi additionally has a use that demands a HeapObject (the -2.js repro routes it through a secondary Phi needing a HeapNumber), Maglev materializes it only as a HeapObject and never as a Smi, violating the invariant that a value stored into a Smi field is actually a Smi.

The fix moves RecordSmiUse(object) to run unconditionally before the constant early-return (and removes it from the kTagged case), so the Smi requirement is always recorded and the elided-Smi-check path can no longer silently drop it.

Key insight
The single mistake was recording the Smi use requirement only on the non-constant CheckSmi-emitting path, so a Phi with a constant Int32 alternative had its Smi check elided and its Smi materialization requirement lost; the fix hoists RecordSmiUse above the constant early-return so the requirement is registered regardless of whether the runtime check is elided.

Attack Path

  1. Create a Phi with a constant Int32 alternative In optimized code, route a loop Phi (e.g. phi = 42 incremented in a loop) through an assignment guarded by CheckValueEqualsInt32 (cst = phi), recording an in-range Int32 constant alternative.
  2. Feed the Phi to a Smi-typed store Assign the same Phi into a field that expects a Smi (o1.x = phi); BuildCheckSmi sees the in-range constant, returns early, and the pre-patch path never calls RecordSmiUse.
  3. Add a HeapObject-requiring use Route the Phi (via a secondary Phi such as c ? phi : non_smi_int32) into a store that requires a HeapNumber/HeapObject (o2.y = phi2), which forces materialization as a tagged HeapObject.
  4. Materialize the shared value wrong Because no Smi use was recorded, Maglev materializes the Phi only as a HeapObject and stores that pointer into the Smi field, so the field now holds a HeapObject where a Smi is assumed — a tag/type confusion (inferred as the exploitable primitive; the diff and --verify-heap repro demonstrate the heap-invariant violation, not a full primitive).

Impact Assessment

An attacker running crafted JavaScript that is optimized by Maglev can cause a value to be stored into a Smi-typed field as a HeapObject, breaking the Smi tagging invariant and yielding type confusion in the renderer process. The advisory labels this “Inappropriate implementation in V8” (LogicError) and states no exploited-in-the-wild status; the precondition is only that the function tiers up to Maglev with the specific Phi/CheckValueEqualsInt32 pattern. Escalation from the tag confusion to a memory read/write or code execution is (inferred) and not shown by the patch.

Changed Functions

FunctionChangeNotes
for
test/mjsunit/maglev/regress-490558172-1.js
modified
if
test/mjsunit/maglev/regress-490558172-1.js
modified
for
test/mjsunit/maglev/regress-490558172-2.js
modified
if
test/mjsunit/maglev/regress-490558172-2.js
modified

Files Changed

  • src/maglev/maglev-graph-builder.cc
  • test/mjsunit/maglev/regress-490558172-1.js
  • test/mjsunit/maglev/regress-490558172-2.js

Audit Directions

  • Elided-check vs recorded-use divergence
    Audit every place where an optimization elides a runtime type/tag check for a constant or proven value, confirming that any materialization/representation requirement tied to that use is still recorded on the elided path.
  • Constant-alternative interactions
    Review nodes that register constant Int32/Smi alternatives (CheckValueEqualsInt32 and peers) for downstream consumers that assume the alternative implies a specific tagged representation.
  • Multi-use Phi materialization
    Look for Phis with mixed Smi- and HeapObject-requiring uses, verifying UseRequiresSmi/RecordSmiUse bookkeeping forces a materialization that satisfies all uses rather than the last one seen.
From 0fff08a529f436a8eedb8d1f1b4cc5bcee4bf670 Mon Sep 17 00:00:00 2001
From: Darius Mercadier <[email protected]>
Date: Wed, 11 Mar 2026 07:57:37 +0100
Subject: [PATCH] [maglev] Stop eliding Smi checks because of CheckValueEqualsInt32

If a Phi goes through a CheckValueEqualsInt32 then we record a
constant int32 alternative for this Phi.

If the Phi is then the input to a BuildCheckSmi, we'll update the KNA
to record that this Phi is a Smi but won't insert a Smi check if the
int32 constant alternative is in range. Notably, this doesn't call
UseRequiresSmi, which means that when materializing this Phi, we won't
really know that it needs to be a Smi rather than a HeapObject.

Where is goes wrong is if this phi has a use that requires it to be
materialized to HeapObject (this isn't easy to construct, cf the -2.js
repro in this CL: I used a secondary Phi to trigger this), then we'll
always materialize it as a HeapObject and never as a Smi, which can
be wrong if we store it in a Smi field.

Fixed: 490558172
Change-Id: Idcc7077187a3e952f7d3eb42150525d33cefe430
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7653635
Auto-Submit: Darius Mercadier <[email protected]>
Reviewed-by: Victor Gomes <[email protected]>
Commit-Queue: Victor Gomes <[email protected]>
Cr-Commit-Position: refs/heads/main@{#105717}
---

diff --git a/src/maglev/maglev-graph-builder.cc b/src/maglev/maglev-graph-builder.cc
index 9f8d4c9..6dfedbd 100644
--- a/src/maglev/maglev-graph-builder.cc
+++ b/src/maglev/maglev-graph-builder.cc
@@ -4183,6 +4183,7 @@
     return EmitUnconditionalDeopt(DeoptimizeReason::kSmi);
   }
   if (EnsureType(object, NodeType::kSmi) && elidable) return object;
+  RecordSmiUse(object);
   // For constants, we may be able to skip the runtime check.
   if (std::optional<int32_t> constant_value = TryGetInt32Constant(object)) {
     if (Smi::IsValid(constant_value.value())) return object;
@@ -4203,7 +4204,6 @@
       AddNewNodeNoInputConversion<CheckHoleyFloat64IsSmi>({object});
       break;
     case ValueRepresentation::kTagged:
-      RecordSmiUse(object);
       AddNewNodeNoInputConversion<CheckSmi>({object});
       break;
     case ValueRepresentation::kIntPtr:
diff --git a/test/mjsunit/maglev/regress-490558172-1.js b/test/mjsunit/maglev/regress-490558172-1.js
new file mode 100644
index 0000000..6f5fd14
--- /dev/null
+++ b/test/mjsunit/maglev/regress-490558172-1.js
@@ -0,0 +1,32 @@
+// 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 --verify-heap
+
+let cst = 11;
+
+let o = { x : 17 };
+o.x = 25;
+
+function foo(c) {
+  for (let i = 0; i < 42; i++) {
+    if (i == 11) {
+      cst = i; // Will insert a CheckValueEqualsInt32
+      o.x = i; // Will not insert a CheckSmi because CheckValueEqualsInt32
+               // registers an Smi-sized Int32 constant alternative.
+    }
+  }
+}
+
+%PrepareFunctionForOptimization(foo);
+foo(true);
+foo(true);
+
+o.x = 42;
+
+%OptimizeMaglevOnNextCall(foo);
+foo(true);
+
+assertEquals(11, cst);
+assertEquals(11, o.x);
diff --git a/test/mjsunit/maglev/regress-490558172-2.js b/test/mjsunit/maglev/regress-490558172-2.js
new file mode 100644
index 0000000..f6fdb91
--- /dev/null
+++ b/test/mjsunit/maglev/regress-490558172-2.js
@@ -0,0 +1,49 @@
+// 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 --verify-heap --maglev
+
+const non_smi_int32 = 0x40000000;
+let cst = 47;
+
+let o1 = { x : 17 };
+o1.x = 25;
+
+let o2 = { y : "abc" };
+o2.y = [];
+
+function foo(c) {
+  let phi = 42;
+  for (let i = 0; i < 10; i++) {
+    if (i == 5) {
+      cst = phi;  // Will insert a CheckValueEqualsInt32
+      // Requires Smi.
+      o1.x = phi; // Will not insert a CheckSmi because CheckValueEqualsInt32
+                  // registers an Smi-sized Int32 constant alternative.
+
+    }
+    phi++;
+  }
+  let phi2 = c ? phi : non_smi_int32;
+  // Requires HeapNumber
+  o2.y = phi2;
+}
+
+%PrepareFunctionForOptimization(foo);
+foo(false);
+foo(false);
+
+%OptimizeMaglevOnNextCall(foo);
+foo(false);
+assertOptimized(foo);
+
+assertEquals(47, cst);
+assertEquals(47, o1.x);
+assertEquals(non_smi_int32, o2.y);
+
+foo(true);
+assertUnoptimized(foo);
+assertEquals(47, cst);
+assertEquals(47, o1.x);
+assertEquals(52, o2.y);
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/test/mjsunit/maglev/regress-490558172-1.js b/test/mjsunit/maglev/regress-490558172-1.js
new file mode 100644
index 0000000..6f5fd14
--- /dev/null
+++ b/test/mjsunit/maglev/regress-490558172-1.js
@@ -0,0 +1,32 @@
+// 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 --verify-heap
+
+let cst = 11;
+
+let o = { x : 17 };
+o.x = 25;
+
+function foo(c) {
+  for (let i = 0; i < 42; i++) {
+    if (i == 11) {
+      cst = i; // Will insert a CheckValueEqualsInt32
+      o.x = i; // Will not insert a CheckSmi because CheckValueEqualsInt32
+               // registers an Smi-sized Int32 constant alternative.
+    }
+  }
+}
+
+%PrepareFunctionForOptimization(foo);
+foo(true);
+foo(true);
+
+o.x = 42;
+
+%OptimizeMaglevOnNextCall(foo);
+foo(true);
+
+assertEquals(11, cst);
+assertEquals(11, o.x);
diff --git a/test/mjsunit/maglev/regress-490558172-2.js b/test/mjsunit/maglev/regress-490558172-2.js
new file mode 100644
index 0000000..f6fdb91
--- /dev/null
+++ b/test/mjsunit/maglev/regress-490558172-2.js
@@ -0,0 +1,49 @@
+// 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 --verify-heap --maglev
+
+const non_smi_int32 = 0x40000000;
+let cst = 47;
+
+let o1 = { x : 17 };
+o1.x = 25;
+
+let o2 = { y : "abc" };
+o2.y = [];
+
+function foo(c) {
+  let phi = 42;
+  for (let i = 0; i < 10; i++) {
+    if (i == 5) {
+      cst = phi;  // Will insert a CheckValueEqualsInt32
+      // Requires Smi.
+      o1.x = phi; // Will not insert a CheckSmi because CheckValueEqualsInt32
+                  // registers an Smi-sized Int32 constant alternative.
+
+    }
+    phi++;
+  }
+  let phi2 = c ? phi : non_smi_int32;
+  // Requires HeapNumber
+  o2.y = phi2;
+}
+
+%PrepareFunctionForOptimization(foo);
+foo(false);
+foo(false);
+
+%OptimizeMaglevOnNextCall(foo);
+foo(false);
+assertOptimized(foo);
+
+assertEquals(47, cst);
+assertEquals(47, o1.x);
+assertEquals(non_smi_int32, o2.y);
+
+foo(true);
+assertUnoptimized(foo);
+assertEquals(47, cst);
+assertEquals(47, o1.x);
+assertEquals(52, o2.y);
Loading diff…

Original Bug Report

reported by [email protected]

Wrong CheckSmi elision because of CheckValueEqualsInt32

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

Fuzzer: None Job Type: linux_asan_d8_dbg Platform Id: linux

Crash Type: DCHECK failure Crash Address: Crash State: value->Is<Phi>() implies value->StaticTypeIs(broker(), NodeType::kSmi) || value- v8::internal::maglev::MaglevGraphBuilder::CanElideWriteBarrier v8::internal::maglev::MaglevGraphBuilder::BuildStoreTaggedFieldNoWriteBarrier

Sanitizer: address (ASAN)

Regressed: https://clusterfuzz.com/revisions?job=linux_asan_d8_dbg&range=105606:105607

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

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