Chrome · V8
CVE-2026-85045
Race in V8
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifsrc/maglev/maglev-code-generator.cc |
modified | |
fortest/mjsunit/maglev/regress-547819997.js |
modified |
Files Changed
src/maglev/maglev-code-generator.ccsrc/maglev/maglev-ir.htest/mjsunit/maglev/regress-547819997.js
Patch
From 7d5eb1df2a721c8fe3aa03b999464c4b86242080 Mon Sep 17 00:00:00 2001 From: Victor Gomes <[email protected]> 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 <[email protected]> Commit-Queue: Victor Gomes <[email protected]> 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.
References
On This Page