CVE-2026-4461
Overview
Background
- Maglev
- V8’s mid-tier optimizing JIT that builds an SSA graph with
Phinodes and tracks per-node type/representation knowledge (KNA, “known node aspects”). - `Phi` representation & materialization
- a
Phican be kept as an untaggedInt32, aSmi, or a taggedHeapObject/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 constantInt32alternative for thatPhi(the value is known to equal a specific in-range integer). - `BuildCheckSmi` / `RecordSmiUse` / `CheckSmi`
BuildCheckSmiensures a value is aSmi;RecordSmiUsemarks that a use requires the value to be materialized as aSmi;CheckSmiis the runtime guard, whichBuildCheckSmimay 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.
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
- Create a Phi with a constant Int32 alternative
In optimized code, route a loop
Phi(e.g.phi = 42incremented in a loop) through an assignment guarded byCheckValueEqualsInt32(cst = phi), recording an in-rangeInt32constant alternative. - Feed the Phi to a Smi-typed store
Assign the same
Phiinto a field that expects aSmi(o1.x = phi);BuildCheckSmisees the in-range constant, returns early, and the pre-patch path never callsRecordSmiUse. - Add a HeapObject-requiring use
Route the
Phi(via a secondaryPhisuch asc ? phi : non_smi_int32) into a store that requires aHeapNumber/HeapObject(o2.y = phi2), which forces materialization as a taggedHeapObject. - Materialize the shared value wrong
Because no Smi use was recorded, Maglev materializes the
Phionly as aHeapObjectand stores that pointer into theSmifield, so the field now holds aHeapObjectwhere aSmiis assumed — a tag/type confusion (inferred as the exploitable primitive; the diff and--verify-heaprepro demonstrate the heap-invariant violation, not a full primitive).
Impact Assessment
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
| Function | Change | Notes |
|---|---|---|
fortest/mjsunit/maglev/regress-490558172-1.js |
modified | |
iftest/mjsunit/maglev/regress-490558172-1.js |
modified | |
fortest/mjsunit/maglev/regress-490558172-2.js |
modified | |
iftest/mjsunit/maglev/regress-490558172-2.js |
modified |
Files Changed
src/maglev/maglev-graph-builder.cctest/mjsunit/maglev/regress-490558172-1.jstest/mjsunit/maglev/regress-490558172-2.js
Audit Directions
- Elided-check vs recorded-use divergenceAudit 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 interactionsReview nodes that register constant
Int32/Smi alternatives (CheckValueEqualsInt32and peers) for downstream consumers that assume the alternative implies a specific tagged representation. - Multi-use Phi materializationLook for
Phis with mixed Smi- and HeapObject-requiring uses, verifyingUseRequiresSmi/RecordSmiUsebookkeeping forces a materialization that satisfies all uses rather than the last one seen.
Patch
From 0fff08a529f436a8eedb8d1f1b4cc5bcee4bf670 Mon Sep 17 00:00:00 2001
From: Darius Mercadier <dmercadier@chromium.org>
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 <dmercadier@chromium.org>
Reviewed-by: Victor Gomes <victorgomes@chromium.org>
Commit-Queue: Victor Gomes <victorgomes@chromium.org>
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);
Regression Test / PoC
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);
Original Bug Report
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.