High chrome Race 🔧 Commit mapped

Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactRace condition in V8
DescriptionRace condition in V8
ComponentV8
Bug ClassRace
Tracker547819997
Fix commit7d5eb1df2a72 (v8/v8) +55/-24
CISA KEVNot listed
CreditedBrendan Dolan-Gavitt, XBOW
Disclosed2026-09-03

Changed Functions

FunctionChangeNotes
if
src/maglev/maglev-code-generator.cc
modified
for
test/mjsunit/maglev/regress-547819997.js
modified

Files Changed

  • src/maglev/maglev-code-generator.cc
  • src/maglev/maglev-ir.h
  • test/mjsunit/maglev/regress-547819997.js
From 7d5eb1df2a721c8fe3aa03b999464c4b86242080 Mon Sep 17 00:00:00 2001
From: Victor Gomes <victorgomes@chromium.org>
Date: Wed, 19 Aug 2026 14:06:55 +0200
Subject: [PATCH] [maglev] Materialize a fresh HeapNumber per deoptimization

This matches what Turbolev graph builder already does to Maglev.

Fixed: 547819997
Change-Id: Iafc1185ef96db8816db36b295a3a4b7e528f9863
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/8255554
Reviewed-by: Leszek Swirski <leszeks@chromium.org>
Commit-Queue: Victor Gomes <victorgomes@chromium.org>
Cr-Commit-Position: refs/heads/main@{#109347}
---

diff --git a/src/maglev/maglev-code-generator.cc b/src/maglev/maglev-code-generator.cc
index 6ebc3c7..32899ba 100644
--- a/src/maglev/maglev-code-generator.cc
+++ b/src/maglev/maglev-code-generator.cc
@@ -1557,17 +1557,9 @@
     return kNotDuplicated;
   }
 
-  void BuildHeapNumber(const VirtualObject* vobject) {
-    DCHECK_EQ(vobject->object_type(), vobj::ObjectType::kHeapNumber);
-    ValueNode* value_node = vobject->get(offsetof(HeapNumber, value_));
-    return BuildHeapNumber(value_node->Cast<Float64Constant>()->value());
-  }
-
-  void BuildHeapNumber(Float64 number) {
-    DirectHandle<Object> value =
-        local_isolate_->factory()->NewHeapNumberFromBits<AllocationType::kOld>(
-            number.get_bits());
-    translation_array_builder_->StoreLiteral(GetDeoptLiteral(*value));
+  int CreateUnduplicatableId() {
+    object_ids_.push_back(kNotDuplicated);
+    return kNotDuplicated;
   }
 
   void BuildNestedValue(const ValueNode* value,
@@ -1622,13 +1614,16 @@
                           const InputLocation*& input_location,
                           const VirtualObjectList& virtual_objects) {
     vobj::ObjectType object_type = object->object_type();
-    if (object_type == vobj::ObjectType::kHeapNumber) {
-      // TODO(jgruber): Could we use the standard path below instead?
-      return BuildHeapNumber(object);
-    }
     DCHECK_NOT_NULL(object->allocation());
+    // HeapNumbers may be mutable object fields; each materialization must
+    // create a fresh box, so they are never deduplicated.
+    // TODO(victorgomes): Constrain which objects may contain mutable
+    // HeapNumbers. Immutable HeapNumbers can be stored as a literal object
+    // instead of a captured object.
     int dup_id =
-        GetDuplicatedId(reinterpret_cast<intptr_t>(object->allocation()));
+        object_type == vobj::ObjectType::kHeapNumber
+            ? CreateUnduplicatableId()
+            : GetDuplicatedId(reinterpret_cast<intptr_t>(object->allocation()));
     if (dup_id != kNotDuplicated) {
       translation_array_builder_->DuplicateObject(dup_id);
       object->ForEachNestedRuntimeInput(
@@ -1796,7 +1791,7 @@
   ZoneVector<IndirectHandle<TrustedObject>>* protected_deopt_literals_vector_;
   ZoneVector<IndirectHandle<Object>>* deopt_literals_vector_;
 
-  static const int kNotDuplicated = -1;
+  static constexpr int kNotDuplicated = -1;
   std::vector<intptr_t> object_ids_;
 };
 
diff --git a/src/maglev/maglev-ir.h b/src/maglev/maglev-ir.h
index fc76ae5..c9f1bd8 100644
--- a/src/maglev/maglev-ir.h
+++ b/src/maglev/maglev-ir.h
@@ -5874,11 +5874,9 @@
         vobj::Field snd = FieldForOffset(offsetof(ConsString, second_));
         return callback(slots_[snd.slot_index], snd);
       }
-      if (object_type() == vobj::ObjectType::kHeapNumber) {
-        // HeapNumber materialization creates a literal object instead of
-        // slot traversal.
-        return true;
-      }
+      // TODO(victorgomes): Constrain which objects may contain mutable
+      // HeapNumbers. Immutable HeapNumbers can be stored as a literal object
+      // instead of traversing their slots.
     }
     for (int i = 0; i < slot_count(); i++) {
       vobj::Field field = FieldForSlot(i);
@@ -6243,8 +6241,8 @@
 
 struct VirtualHeapNumberShape : VirtualPrimitiveHeapObjectShape {
   using T = HeapNumber;
-  // Special handling needed; deopt materialization uses a special path.
-  // TODO(jgruber): .. but could it take the standard path instead?
+  // Special handling needed; instances may be mutable object fields and thus
+  // must never be deduplicated in deopt frames.
   static constexpr vobj::ObjectType kObjectType = vobj::ObjectType::kHeapNumber;
 #define FIELD_LIST(V) V(value, offsetof(T, value_), vobj::FieldType::kFloat64)
   DEF_SHAPE(VirtualPrimitiveHeapObjectShape, FIELD_LIST);
diff --git a/test/mjsunit/maglev/regress-547819997.js b/test/mjsunit/maglev/regress-547819997.js
new file mode 100644
index 0000000..43a8e39
--- /dev/null
+++ b/test/mjsunit/maglev/regress-547819997.js
@@ -0,0 +1,38 @@
+// 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 --maglev
+
+const boxes = [];
+
+// Never called during warm-up, so this call site has no feedback and Maglev
+// emits an unconditional deopt for it. The literal below is therefore only
+// used by deopt frames and gets elided by escape analysis.
+function sink(o) {
+  boxes.push(o);
+}
+
+function foo(depth, take) {
+  const o = {x: 1.5};
+  if (depth > 0) foo(depth - 1, take);
+  if (take) sink(o);
+}
+
+%PrepareFunctionForOptimization(foo);
+foo(3, false);
+foo(3, false);
+%OptimizeMaglevOnNextCall(foo);
+foo(3, false);
+
+// Deopts the innermost activation eagerly and the outer ones lazily, so every
+// activation materializes its own object.
+foo(3, true);
+
+assertEquals(4, boxes.length);
+for (let i = 0; i < boxes.length; i++) {
+  boxes[i].x = i;
+}
+for (let i = 0; i < boxes.length; i++) {
+  assertEquals(i, boxes[i].x);
+}
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/test/mjsunit/maglev/regress-547819997.js b/test/mjsunit/maglev/regress-547819997.js
new file mode 100644
index 0000000..43a8e39
--- /dev/null
+++ b/test/mjsunit/maglev/regress-547819997.js
@@ -0,0 +1,38 @@
+// 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 --maglev
+
+const boxes = [];
+
+// Never called during warm-up, so this call site has no feedback and Maglev
+// emits an unconditional deopt for it. The literal below is therefore only
+// used by deopt frames and gets elided by escape analysis.
+function sink(o) {
+  boxes.push(o);
+}
+
+function foo(depth, take) {
+  const o = {x: 1.5};
+  if (depth > 0) foo(depth - 1, take);
+  if (take) sink(o);
+}
+
+%PrepareFunctionForOptimization(foo);
+foo(3, false);
+foo(3, false);
+%OptimizeMaglevOnNextCall(foo);
+foo(3, false);
+
+// Deopts the innermost activation eagerly and the outer ones lazily, so every
+// activation materializes its own object.
+foo(3, true);
+
+assertEquals(4, boxes.length);
+for (let i = 0; i < boxes.length; i++) {
+  boxes[i].x = i;
+}
+for (let i = 0; i < boxes.length; i++) {
+  assertEquals(i, boxes[i].x);
+}
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.