Chrome · V8
CVE-2026-4447
Logic Error in V8
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Files Changed
src/maglev/maglev-graph-builder.cc
Patch
From 473f72163411778d823846596837bf6ff5af5a5e Mon Sep 17 00:00:00 2001 From: Darius Mercadier <[email protected]> Date: Tue, 24 Feb 2026 16:54:54 +0100 Subject: [PATCH] [maglev] Preserve HeapObjectness during Phi untagging when required Fixed: 486657483 Change-Id: I351d6049a0cdfe82787b644d03c9ae0bcf802b11 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7604253 Commit-Queue: Darius Mercadier <[email protected]> Reviewed-by: Leszek Swirski <[email protected]> Cr-Commit-Position: refs/heads/main@{#105439} --- diff --git a/src/maglev/maglev-graph-builder.cc b/src/maglev/maglev-graph-builder.cc index f8bf0d9..9cc9044 100644 --- a/src/maglev/maglev-graph-builder.cc +++ b/src/maglev/maglev-graph-builder.cc @@ -1824,9 +1824,9 @@ // This node may unwrap ThinStrings. ValueNode* maybe_unwrapping_node; - GET_VALUE_OR_ABORT( - maybe_unwrapping_node, - AddNewNode<CheckedInternalizedString>({node}, GetCheckType(old_type))); + GET_VALUE_OR_ABORT(maybe_unwrapping_node, + AddNewNode<CheckedInternalizedString>( + {node}, GetCheckType(old_type, node))); known_info->alternative().set_checked_value(maybe_unwrapping_node); current_interpreter_frame_.set(reg, maybe_unwrapping_node); @@ -3571,7 +3571,7 @@ } } - enum CheckType type = GetCheckType(node_type); + enum CheckType type = GetCheckType(node_type, value); return AddNewNode<TestUndetectable>({value}, type); } @@ -4167,6 +4167,13 @@ IntersectType(GetType(object), NodeType::kAnyHeapObject))) { return EmitUnconditionalDeopt(DeoptimizeReason::kSmi); } + if (Phi* phi = object->TryCast<Phi>()) { + // Through phi untagging and the default float64->tagged canonicalization, a + // Phi could currently be a HeapObject but be retagged as a Smi after phi + // representation selection. If we request this Phi has a HeapObject, we + // record it so that when we retag it we preserve its HeapObjectness. + phi->SetUseRequiresHeapObject(); + } if (EnsureType(object, NodeType::kAnyHeapObject)) return ReduceResult::Done(); return AddNewNode<CheckHeapObject>({object}); } @@ -4182,7 +4189,8 @@ if (EnsureType(object, NodeType::kSeqOneByteString, &known_type)) { return ReduceResult::Done(); } - return AddNewNode<CheckSeqOneByteString>({object}, GetCheckType(known_type)); + return AddNewNode<CheckSeqOneByteString>({object}, + GetCheckType(known_type, object)); } ReduceResult MaglevGraphBuilder::BuildCheckString(ValueNode* object) { @@ -4195,7 +4203,7 @@ if (EnsureType(object, NodeType::kString, &known_type)) { return ReduceResult::Done(); } - return AddNewNode<CheckString>({object}, GetCheckType(known_type)); + return AddNewNode<CheckString>({object}, GetCheckType(known_type, object)); } ReduceResult MaglevGraphBuilder::BuildCheckStringOrStringWrapper( @@ -4209,8 +4217,8 @@ } if (EnsureType(object, NodeType::kStringOrStringWrapper, &known_type)) return ReduceResult::Done(); - return AddNewNode<CheckStringOrStringWrapper>({object}, - GetCheckType(known_type)); + return AddNewNode<CheckStringOrStringWrapper>( + {object}, GetCheckType(known_type, object)); } ReduceResult MaglevGraphBuilder::BuildCheckStringOrOddball(ValueNode* object) { @@ -4224,7 +4232,8 @@ if (EnsureType(object, NodeType::kStringOrOddball, &known_type)) { return ReduceResult::Done(); } - return AddNewNode<CheckStringOrOddball>({object}, GetCheckType(known_type)); + return AddNewNode<CheckStringOrOddball>({object}, + GetCheckType(known_type, object)); } ReduceResult MaglevGraphBuilder::BuildCheckNumber(ValueNode* object) { @@ -4248,7 +4257,7 @@ } if (EnsureType(object, NodeType::kSymbol, &known_type)) return ReduceResult::Done(); - return AddNewNode<CheckSymbol>({object}, GetCheckType(known_type)); + return AddNewNode<CheckSymbol>({object}, GetCheckType(known_type, object)); } ReduceResult MaglevGraphBuilder::BuildCheckJSFunction(ValueNode* object) { @@ -4260,9 +4269,9 @@ } if (EnsureType(object, NodeType::kJSFunction, &known_type)) return ReduceResult::Done(); - return AddNewNode<CheckInstanceType>({object}, GetCheckType(known_type), - FIRST_JS_FUNCTION_TYPE, - LAST_JS_FUNCTION_TYPE); + return AddNewNode<CheckInstanceType>( + {object}, GetCheckType(known_type, object), FIRST_JS_FUNCTION_TYPE, + LAST_JS_FUNCTION_TYPE); } ReduceResult MaglevGraphBuilder::BuildCheckJSReceiver(ValueNode* object) { @@ -4274,9 +4283,9 @@ } if (EnsureType(object, NodeType::kJSReceiver, &known_type)) return ReduceResult::Done(); - return AddNewNode<CheckInstanceType>({object}, GetCheckType(known_type), - FIRST_JS_RECEIVER_TYPE, - LAST_JS_RECEIVER_TYPE); + return AddNewNode<CheckInstanceType>( + {object}, GetCheckType(known_type, object), FIRST_JS_RECEIVER_TYPE, + LAST_JS_RECEIVER_TYPE); } ReduceResult MaglevGraphBuilder::BuildCheckJSReceiverOrNullOrUndefined( @@ -4292,8 +4301,8 @@ if (EnsureType(object, NodeType::kJSReceiverOrNullOrUndefined, &known_type)) { return ReduceResult::Done(); } - return AddNewNode<CheckJSReceiverOrNullOrUndefined>({object}, - GetCheckType(known_type)); + return AddNewNode<CheckJSReceiverOrNullOrUndefined>( + {object}, GetCheckType(known_type, object)); } ReduceResult MaglevGraphBuilder::BuildCheckMaps( @@ -4313,17 +4322,20 @@ // Emit checks. if (merger.emit_check_with_migration() && !migration_done_outside) { RETURN_IF_ABORT(AddNewNode<CheckMapsWithMigration>( - {object}, merger.intersect_set(), GetCheckType(known_info->type()))); + {object}, merger.intersect_set(), + GetCheckType(known_info->type(), object))); } else if (has_deprecated_map_without_migration_target && !migration_done_outside) { RETURN_IF_ABORT(AddNewNode<CheckMapsWithMigrationAndDeopt>( - {object}, merger.intersect_set(), GetCheckType(known_info->type()))); + {object}, merger.intersect_set(), + GetCheckType(known_info->type(), object))); } else if (map) { RETURN_IF_ABORT(AddNewNode<CheckMapsWithAlreadyLoadedMap>( {object, *map}, merger.intersect_set())); } else { - RETURN_IF_ABORT(AddNewNode<CheckMaps>({object}, merger.intersect_set(), - GetCheckType(known_info->type()))); + RETURN_IF_ABORT( + AddNewNode<CheckMaps>({object}, merger.intersect_set(), + GetCheckType(known_info->type(), object))); } merger.UpdateKnownNodeAspects(object, known_node_aspects()); @@ -5742,7 +5754,7 @@ // TODO(leszeks): Cache this knowledge/converted value somehow on // the node info. return AddNewNodeNoInputConversion<CheckedObjectToIndex>( - {object}, GetCheckType(old_type)); + {object}, GetCheckType(old_type, object)); } case ValueRepresentation::kInt32: // Already good. @@ -8107,7 +8119,7 @@ return ReduceResult::Done(); case TypeOfFeedback::kFunction: RETURN_IF_ABORT(AddNewNode<CheckDetectableCallable>( - {value}, GetCheckType(GetType(value)))); + {value}, GetCheckType(GetType(value), value))); EnsureType(value, NodeType::kCallable); SetAccumulator(GetRootConstant(RootIndex::kfunction_string)); return ReduceResult::Done(); @@ -13979,7 +13991,7 @@ return value; } return AddNewNode<std::conditional_t<flip, ToBooleanLogicalNot, ToBoolean>>( - {value}, GetCheckType(value_type)); + {value}, GetCheckType(value_type, value)); } MaybeReduceResult MaglevGraphBuilder::TryBuildFastInstanceOfWithFeedback( @@ -14143,8 +14155,9 @@ destination); } else { ValueNode* object; - GET_VALUE_OR_ABORT(object, AddNewNode<ToObject>({GetContext(), value}, - GetCheckType(old_type))); + GET_VALUE_OR_ABORT(object, + AddNewNode<ToObject>({GetContext(), value}, + GetCheckType(old_type, value)));
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/test/mjsunit/maglev/regress-486657483-1.js b/test/mjsunit/maglev/regress-486657483-1.js
new file mode 100644
index 0000000..b54e22e
--- /dev/null
+++ b/test/mjsunit/maglev/regress-486657483-1.js
@@ -0,0 +1,28 @@
+// 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 --expose-gc --verify-heap
+
+function make_heapnum(v) { const a=[1.1]; a[0]=v; return a[0]; }
+
+const H0 = make_heapnum(-1.0);
+const H1 = make_heapnum(-1.0);
+
+function f(c, target) {
+ let v = c ? H0 : H1;
+ let y = v + 0.1;
+ target.p = v;
+
+ return y;
+}
+
+%PrepareFunctionForOptimization(f);
+
+let target = {p:{x:1}};
+f(0, target);
+
+%OptimizeMaglevOnNextCall(f);
+gc();
+
+(f(1, target));
diff --git a/test/mjsunit/maglev/regress-486657483-2.js b/test/mjsunit/maglev/regress-486657483-2.js
new file mode 100644
index 0000000..f7bd3ee
--- /dev/null
+++ b/test/mjsunit/maglev/regress-486657483-2.js
@@ -0,0 +1,27 @@
+// 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
+
+function make_heapnum(v) { const a=[1.1]; a[0]=v; return a[0]; }
+
+const H0 = make_heapnum(24440.0);
+const H1 = make_heapnum(-1.0);
+
+function foo() {
+ let x = H0;
+ for (let i = 0; i < 1; ++i) {
+ if (i) x = H1;
+ x + 0.25;
+ }
+ let c = !!x;
+
+ return c;
+}
+
+%PrepareFunctionForOptimization(foo);
+assertEquals(true, foo());
+
+%OptimizeMaglevOnNextCall(foo);
+assertEquals(true, foo());
diff --git a/test/mjsunit/maglev/regress-486657483-3.js b/test/mjsunit/maglev/regress-486657483-3.js
new file mode 100644
index 0000000..f7bd3ee
--- /dev/null
+++ b/test/mjsunit/maglev/regress-486657483-3.js
@@ -0,0 +1,27 @@
+// 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
+
+function make_heapnum(v) { const a=[1.1]; a[0]=v; return a[0]; }
+
+const H0 = make_heapnum(24440.0);
+const H1 = make_heapnum(-1.0);
+
+function foo() {
+ let x = H0;
+ for (let i = 0; i < 1; ++i) {
+ if (i) x = H1;
+ x + 0.25;
+ }
+ let c = !!x;
+
+ return c;
+}
+
+%PrepareFunctionForOptimization(foo);
+assertEquals(true, foo());
+
+%OptimizeMaglevOnNextCall(foo);
+assertEquals(true, foo());
diff --git a/test/mjsunit/maglev/regress-486657483-4.js b/test/mjsunit/maglev/regress-486657483-4.js
new file mode 100644
index 0000000..aeab2ad
--- /dev/null
+++ b/test/mjsunit/maglev/regress-486657483-4.js
@@ -0,0 +1,25 @@
+// 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
+
+function make_heapnum(v) { const a=[1.1]; a[0]=v; return a[0]; }
+
+const H0 = make_heapnum(24440.0);
+const H1 = make_heapnum(-1.0);
+
+function foo(c, o) {
+ let x = c ? H1 : H0;
+ x + 2;
+ o.x = x;
+}
+
+let o = { x : "abc" };
+o.x = []; // making non-const and removing field map.
+
+%PrepareFunctionForOptimization(foo);
+foo(true, o);
+
+%OptimizeMaglevOnNextCall(foo);
+foo(true, o);
diff --git a/test/mjsunit/maglev/regress-486657483-5.js b/test/mjsunit/maglev/regress-486657483-5.js
new file mode 100644
index 0000000..36bc347
--- /dev/null
+++ b/test/mjsunit/maglev/regress-486657483-5.js
@@ -0,0 +1,36 @@
+// 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
+
+function make_heapnum(v) { const a=[1.1]; a[0]=v; return a[0]; }
+
+const H0 = 42;//make_heapnum(24440.0);
+const H1 = make_heapnum(-1.0);
+
+function foo(c, o) {
+ let x = c ? H1 : H0;
+ x + 2;
+ if (c) {
+ // Will have HeapObject field representation.
+ o.x = x;
+ } else {
+ // Will have Smi field representation.
+ o.y = x;
+ }
+}
+
+let o_heapobj_field = { x : "abc" };
+o_heapobj_field.x = []; // making non-const and removing field map.
+
+let o_smi_field = { y : 17 };
+o_smi_field.y = 25; // making non-const
+
+%PrepareFunctionForOptimization(foo);
+foo(true, o_heapobj_field);
+foo(false, o_smi_field);
+
+%OptimizeMaglevOnNextCall(foo);
+foo(true, o_heapobj_field);
+foo(false, o_smi_field);
Loading diff…
Original Bug Report
reported by [email protected]
Incorrect Maglev assumption leading to SIGSEGV
VERSION
V8 Git Commit: c0a41078e69f23668c8d34c61f286a1b5b211f19
REPRODUCTION CASE
function make_heapnum(v) { const a=[1.1]; a[0]=v; return a[0]; }
const H0 = make_heapnum(-1.0);
const H1 = make_heapnum(-1.0);
function f(c, target) {
let v = c ? H0 : H1;
let y = v + 0.1;
target.p = v;
return y;
}
%PrepareFunctionForOptimization(f);
let target = {p:{x:1}};
f(0, target);
%OptimizeMaglevOnNextCall(f);
gc();
(f(1, target));
d8 --allow-natives-syntax --expose-gc x.js
stack trace:
Received signal 11 SEGV_ACCERR 7ea2fffc0000
==== C stack trace ===============================
out/fuzzbuild/d8(___interceptor_backtrace+0x46)[0x5ab1acf4fb36]
out/fuzzbuild/d8(+0x185b67f)[0x5ab1ad3fb67f]
/lib/x86_64-linux-gnu/libc.so.6(+0x45330)[0x70ee6b645330]
[0x5ab1e00002f2]
[end of stack trace]
Segmentation fault
BISECT
commit 0111c795359d675cfa6522637b6a3a23a0c5f0ce
Author: Darius Mercadier <[email protected]>
Date: Fri Sep 26 16:00:48 2025 +0200
[turbolev] Less expensive WB for non-smis
Bug: 431933185
Change-Id: Ic26217550159c5168353d85ab42806733bb5037c
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/6988732
Reviewed-by: Victor Gomes <[email protected]>
Auto-Submit: Darius Mercadier <[email protected]>
Commit-Queue: Darius Mercadier <[email protected]>
Cr-Commit-Position: refs/heads/main@{#102798}
VULNERABILITY DETAILS
In the repro, at graph-build time the store input is inferred as HeapNumber, therefore NodeTypeCanBe(..., kSmi) returns false, and value_can_be_smi=false is set.
After graph building, MaglevPhiRepresentationSelector runs and:
- It untags the phi to Float64
- Then re-tags it for the store using
Float64ToTagged(kCanonicalizeSmi)- src/maglev/maglev-phi-representation-selector.cc:1355
That re-tagging can produce a Smi for integral floats inside the Smi range, which invalides the value_can_be_smi=false assumption which leads to a crash inside the optimized WB code for non-smi.
CREDIT INFORMATION
Reporter credit: Erge
References
On This Page