CVE-2026-76038
Overview
Files Changed
src/maglev/maglev-graph-builder.cctest/mjsunit/maglev/regress-541926503.js
Patch
From effbf0fc9555ec832149d9884396b9c2f75a46e1 Mon Sep 17 00:00:00 2001 From: Victor Gomes <[email protected]> Date: Mon, 03 Aug 2026 09:57:06 +0200 Subject: [PATCH] [maglev] Only infer a field type from a stable field map The class map recorded in a descriptor array is only a valid source of type information when it is stable and a stability dependency is installed: for an unstable map the GC widens the field type to Any without deoptimizing dependent code. Fixed: 541926503 Change-Id: I14c738f44613fdf403e188c7092fda2a59a0515c Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/8182709 Auto-Submit: Victor Gomes <[email protected]> Reviewed-by: Darius Mercadier <[email protected]> Commit-Queue: Victor Gomes <[email protected]> Commit-Queue: Darius Mercadier <[email protected]> Cr-Commit-Position: refs/heads/main@{#108990} --- diff --git a/src/maglev/maglev-graph-builder.cc b/src/maglev/maglev-graph-builder.cc index 9a81b79..6cf54d1 100644 --- a/src/maglev/maglev-graph-builder.cc +++ b/src/maglev/maglev-graph-builder.cc @@ -113,9 +113,9 @@ } }; -NodeType NodeTypeFromAccessInfo( - compiler::JSHeapBroker* broker, - const compiler::PropertyAccessInfo& access_info) { +NodeType NodeTypeFromAccessInfo(compiler::JSHeapBroker* broker, + const compiler::PropertyAccessInfo& access_info, + compiler::OptionalMapRef stable_field_map) { if (access_info.field_representation().IsSmi()) { return NodeType::kSmi; } @@ -124,8 +124,8 @@ return NodeType::kHeapNumber; } - if (access_info.field_map().has_value()) { - return StaticTypeForMap(access_info.field_map().value(), broker); + if (stable_field_map.has_value()) { + return StaticTypeForMap(stable_field_map.value(), broker); } const auto type = access_info.field_type(); @@ -4466,13 +4466,14 @@ {heap_number}, static_cast<int>(offsetof(HeapNumber, value_))); } - const auto node_type = NodeTypeFromAccessInfo(this->broker(), access_info); compiler::OptionalMapRef stable_field_map; if (access_info.field_representation().IsHeapObject() && access_info.field_map().has_value() && access_info.field_map().value().is_stable()) { stable_field_map = access_info.field_map(); } + const auto node_type = + NodeTypeFromAccessInfo(this->broker(), access_info, stable_field_map); ValueNode* value; GET_VALUE_OR_ABORT( value, BuildLoadTaggedField(load_source, field_index.offset(), node_type, diff --git a/test/mjsunit/maglev/regress-541926503.js b/test/mjsunit/maglev/regress-541926503.js new file mode 100644 index 0000000..11ec072 --- /dev/null +++ b/test/mjsunit/maglev/regress-541926503.js @@ -0,0 +1,42 @@ +// 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 --maglev + +const getInt32 = DataView.prototype.getInt32; + +function read(holder) { + return getInt32.call(holder.x, 0, true); +} + +function Holder(x) { + this.x = x; +} + +const a = new DataView(new ArrayBuffer(16)); +const b = new DataView(new ArrayBuffer(16)); +a.p = 1; +b.p = 2; + +// `Holder.x` learns the DataView map of `a` and `b` as its class field type. +const holder = new Holder(a); +holder.x = b; + +// Generalizing `p` to double makes that map unstable. +a.p = 1.5; + +%PrepareFunctionForOptimization(read); +read(holder); +read(holder); +%OptimizeMaglevOnNextCall(read); +read(holder); + +// The last instance migrates away, so the class map dies and the GC widens the +// field type to Any without invalidating the optimized code. +b.p = 2.5; +gc(); +gc(); + +holder.x = {a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9}; +assertThrows(() => read(holder), TypeError);
Regression Test / PoC
diff --git a/test/mjsunit/maglev/regress-541926503.js b/test/mjsunit/maglev/regress-541926503.js
new file mode 100644
index 0000000..11ec072
--- /dev/null
+++ b/test/mjsunit/maglev/regress-541926503.js
@@ -0,0 +1,42 @@
+// 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 --maglev
+
+const getInt32 = DataView.prototype.getInt32;
+
+function read(holder) {
+ return getInt32.call(holder.x, 0, true);
+}
+
+function Holder(x) {
+ this.x = x;
+}
+
+const a = new DataView(new ArrayBuffer(16));
+const b = new DataView(new ArrayBuffer(16));
+a.p = 1;
+b.p = 2;
+
+// `Holder.x` learns the DataView map of `a` and `b` as its class field type.
+const holder = new Holder(a);
+holder.x = b;
+
+// Generalizing `p` to double makes that map unstable.
+a.p = 1.5;
+
+%PrepareFunctionForOptimization(read);
+read(holder);
+read(holder);
+%OptimizeMaglevOnNextCall(read);
+read(holder);
+
+// The last instance migrates away, so the class map dies and the GC widens the
+// field type to Any without invalidating the optimized code.
+b.p = 2.5;
+gc();
+gc();
+
+holder.x = {a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9};
+assertThrows(() => read(holder), TypeError);
Original Bug Report
Turbolev: stale DataView field type after GC can lead to arbitrary memory r/w
Steps to reproduce the problem
Please run with attached poc.js:
./out.gn/x64.release/d8 \
--allow-natives-syntax \
--turbolev \
./poc.js
release build was compiled with the following args.gn
is_debug=false
is_asan=true
is_clang=true
target_cpu="x64"
v8_target_cpu="x64"
v8_enable_sandbox=true
v8_enable_memory_corruption_api=true
v8_symbol_level=2
Problem Description
VULNERABILITY DETAILS
Turbolev: stale DataView field type after GC can lead to controllable memory corruption.
NodeTypeFromAccessInfo assigns a precise type whenever a property access has a
class field Map:
if (access_info.field_map().has_value()) {
return StaticTypeForMap(access_info.field_map().value(), broker);
}
However, BuildLoadField only retains that Map and installs a stability
dependency when it is stable:
const auto node_type = NodeTypeFromAccessInfo(this->broker(), access_info);
compiler::OptionalMapRef stable_field_map;
if (access_info.field_representation().IsHeapObject() &&
access_info.field_map().has_value() &&
access_info.field_map().value().is_stable()) {
stable_field_map = access_info.field_map();
}
...
if (stable_field_map.has_value()) {
broker()->dependencies()->DependOnStableMap(stable_field_map.value());
} else {
known_info->IntersectType(node_type);
}
This contradicts the access-info contract in src/compiler/access-info.cc,
which states that the field type may be inferred from the class Map only when
the Map is stable and a stability dependency is added at the use.
The PoC creates two DataViews with the same Map M and stores them in a mutable
holder field, causing the field descriptor to learn FieldType::Class(M).
Changing an added property from Smi to double makes M unstable. TurboLev then
compiles the field load with NodeType::kJSDataView, but no dependency keeps
the unstable Map valid.
After the last object migrates away from M, a natural major GC clears the dead
weak class Map in MarkCompactCollector::SpecialClearMapSlot:
if (map->is_stable() && FieldType::kFieldTypesCanBeClearedOnGC) {
location.store(FieldType::None());
} else {
location.store(FieldType::Any());
}
The unstable field type is widened to Any without invalidating the optimized
code. The holder can then contain an ordinary object while the compiled load
still has the stale kJSDataView type. BuildCheckInstanceType accepts this
known type and omits the runtime JS_DATA_VIEW_TYPE check.
TurboLev consequently reads the ordinary object’s in-object fields as the
DataView byte length and data pointer. The optimized
DataView.prototype.getInt32 and setInt32 calls then provide controlled
4-byte memory reads and writes.
IMPACT
The attached PoC first writes the JavaScript-controlled value 0x41414141 to
a selected mapped address and reads the same four bytes back. It throws before
continuing if the readback is not exactly 0x41414141.
It then performs a second 4-byte store at the selected relative address
0x41414141.
Independent executions with different address randomization produced:
Received signal 11 SEGV_ACCERR 7ea441414141
Received signal 11 SEGV_ACCERR 7a8b41414141
Received signal 11 SEGV_ACCERR 7ea841414141
VERSION
Tested commit:
1d17afafffbb434e04b2ee4bec7ca09989626341 (2026-07-31), Linux x86-64.
BISECT
First bad commit:
ea85967a2149d4f648c60cb26d48ba7b75ecdd79 (2026-06-15),
[maglev] Enrich LoadTaggedField with NodeType metadata.
Summary
Turbolev: stale DataView field type after GC can lead to arbitrary memory r/w
Custom Questions
Type of crash:
controllable crash on renderer
Crash state:
Received signal 11 SEGV_ACCERR 7e9f41414141
==== C stack trace ===============================
./v8/v8/out/Release/d8(__interceptor_backtrace+0x4a)[0x63874af446ea]
./v8/v8/out/Release/d8(+0x6b449c0)[0x6387507a99c0]
/lib/x86_64-linux-gnu/libc.so.6(+0x45330)[0x7005cf445330]
[0x6387b04c1934]
[end of stack trace]
Additional Data
Category: Security
Chrome Channel: Not sure
Regression: N/A \