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 <dmercadier@chromium.org>
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 <dmercadier@chromium.org>
Reviewed-by: Leszek Swirski <leszeks@chromium.org>
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 er...@gmail.com
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 <dmercadier@chromium.org>
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 <victorgomes@chromium.org>
Auto-Submit: Darius Mercadier <dmercadier@chromium.org>
Commit-Queue: Darius Mercadier <dmercadier@chromium.org>
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