Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in V8
DescriptionInappropriate implementation in V8
ComponentV8
Bug ClassLogic Error
Tracker486657483
Fix commit473f72163411 (v8/v8) +310/-74
CISA KEVNot listed
CreditedErge
Disclosed2026-03-18

Files Changed

  • src/maglev/maglev-graph-builder.cc
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

View on issue tracker