High chrome Type Confusion 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactType Confusion in V8
DescriptionType Confusion in V8
ComponentV8
Bug ClassType Confusion
Tracker508811477
Fix commitdf948e672588 (v8/v8) +8/-0
CISA KEVNot listed
CreditedMufeed VH from Winfunc Research (winfunc.com)
Disclosed2026-06-02

Files Changed

  • src/compiler/turboshaft/late-load-elimination-reducer.cc
From df948e6725883d290998e99343a7ef093f781c13 Mon Sep 17 00:00:00 2001
From: Darius Mercadier <[email protected]>
Date: Wed, 06 May 2026 17:29:39 +0200
Subject: [PATCH] [turboshaft] Load elimination: invalidate known maps on Calls

Fixed: 508811477
Change-Id: I090877c8babd5102319d7b3f5b63fefd5af941a9
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7823059
Reviewed-by: Leszek Swirski <[email protected]>
Commit-Queue: Leszek Swirski <[email protected]>
Auto-Submit: Darius Mercadier <[email protected]>
Cr-Commit-Position: refs/heads/main@{#107101}
---

diff --git a/src/compiler/turboshaft/late-load-elimination-reducer.cc b/src/compiler/turboshaft/late-load-elimination-reducer.cc
index 8c7d9cf..79bc56b 100644
--- a/src/compiler/turboshaft/late-load-elimination-reducer.cc
+++ b/src/compiler/turboshaft/late-load-elimination-reducer.cc
@@ -546,6 +546,14 @@
   // The call could modify arbitrary memory, so we invalidate every
   // potentially-aliasing object.
   memory_.InvalidateMaybeAliasing();
+
+  // This call could transition objects, thus invalidating their maps.
+  // TODO(dmercadier): we should only wipe unstable maps here, except that we
+  // don't know which maps are stable or not because we compact maps in
+  // MapMaskAndOr. I'm really not sure how much benefits this MapMaskAndOr
+  // structure brings, so we could instead consider to record exact maps, in
+  // which case we'd be able to only invalidate unstable ones.
+  WipeAllMaps();
 }
 
 // The only time an Allocate should flow into a WordBinop is for Smi checks
Loading diff…

Original Bug Report

reported by [email protected]

V8 Turboshaft late load elimination reuses stale map load after aliased map transition

Security Bug

VULNERABILITY DETAILS

V8 Turboshaft Late Load Elimination can reuse a stale LoadMapField across an aliased map transition. The root cause is in LateLoadEliminationAnalyzer::ProcessStore():

v8/src/compiler/turboshaft/late-load-elimination-reducer.cc

// Updates known stored values first.
if (!invalidate_maybe_aliasing) memory_.Invalidate(store);
memory_.Insert(store);

// Clears map facts only after invalidation already used them.
if (store.offset == HeapObject::kMapOffset && !store.index().valid()) {
  WipeAllMaps();
}

memory_.Invalidate(store) calls InvalidateAtOffset(), which uses object_maps_ through BasesCouldAlias() to decide whether a cached load’s base can alias the store base. Separately, ProcessCall() invalidates memory but does not clear object_maps_:

// The call could modify arbitrary memory, so we invalidate every
// potentially-aliasing object.
memory_.InvalidateMaybeAliasing();

A JavaScript caller can therefore create this sequence in optimized code:

  1. x.x emits CheckMaps(x, A) / AssumeMap(x, A).
  2. An opaque callback transitions x from map A to map B. ProcessCall() wipes cached memory but leaves object_maps_[x] = A stale.
  3. y.x emits CheckMaps(y, B) / AssumeMap(y, B).
  4. x == sentinel lowers to a receiver check that performs a post-call LoadMapField(x) without AssumeMap(x, B), caching the B map load while object_maps_[x] is still A.
  5. y.z = 99 transitions y from B to C. During ProcessStore(), InvalidateAtOffset(kMapOffset, y) sees object_maps_[y] = B and stale object_maps_[x] = A, concludes the bases cannot alias, and does not invalidate the cached LoadMapField(x).
  6. A later x.y CheckMaps(x, B) reuses the stale cached map load. The Turboshaft load-elimination verifier confirms that the replacement differs from the actual runtime load when x === y.

This bypasses the WrongMap deoptimization that should guard optimized code after the map transition. The attached d8 harness deterministically aborts under --turboshaft-verify-load-elimination with:

abort: Turboshaft's load elimination wrongly eliminated a Load

Without the verifier, the same optimized aliased call completes without the WrongMap deopt that occurs in safe variants, demonstrating that the stale map check is accepted.

VERSION

Chrome Version: 149.0.7821.0, local V8/Chromium ASan d8 build from Chromium checkout

Operating System: Ubuntu 22.04.5 LTS x86_64

Build target: out/asan/d8

REPRODUCTION CASE

Attachments:

  • poc.html — Chrome browser PoC for direct local reproduction
  • chrome_parent_lle_browser.log — direct Chrome run showing browser console milestones, LLE trace, verifier abort, and renderer crash signal
  • poc_d8.js — deterministic d8 trigger for faster compiler triage
  • lle_verifier_crash.log — full d8 verifier crash and LateLoadElimination trace
  • lle_noverify.log — same d8 harness without verifier, showing no WrongMap deopt on the aliased call

Browser repro steps:

  1. Build local ASan Chrome from the Chromium checkout:
gn gen out/asan --args='is_asan=true is_debug=false symbol_level=1 use_sysroot=true treat_warnings_as_errors=false'
autoninja -C out/asan chrome
  1. Serve the attached poc.html locally:
python3 -m http.server 8123 --bind 127.0.0.1
  1. Run Chrome directly against the PoC. The --js-flags enable deterministic optimization and the Turboshaft load-elimination verifier; the source tree/binary are otherwise unmodified.
ASAN_OPTIONS='detect_leaks=0:abort_on_error=1:symbolize=1' \
  out/asan/chrome \
    --user-data-dir=/tmp/chrome-lle-poc \
    --no-first-run \
    --no-default-browser-check \
    --disable-background-networking \
    --disable-sync \
    --enable-logging=stderr \
    --js-flags='--allow-natives-syntax --turboshaft --no-maglev --turboshaft-load-elimination --turboshaft-verify-load-elimination --turboshaft-trace-load-elimination --trace-deopt' \
    http://127.0.0.1:8123/poc.html

Observed browser result:

[poc] starting warmup
[poc] optimizing victim on non-aliased call
[poc] non-aliased result: 1142
[poc] triggering aliased call; verifier should abort vulnerable renderer
[poc] target before: {"x":1337}

>>>> InvalidateAtOffset: not invalidating thanks for maps: MemoryAddress{base=0, index=<invalid OpIndex>, offset=0, elem_size_log2=0, size=4}
>> Found potential replacement at offset 72
>>> Confirming replacement
abort: Turboshaft's load elimination wrongly eliminated a Load
Received signal 4 ILL_ILLOPN

The attached chrome_parent_lle_browser.log contains the complete direct browser run.

D8 repro steps:

  1. Build d8 from the Chromium checkout:
gn gen out/asan --args='is_asan=true is_debug=false symbol_level=1 use_sysroot=true treat_warnings_as_errors=false'
autoninja -C out/asan d8
  1. Run the deterministic verifier repro:
ASAN_OPTIONS=detect_leaks=0:abort_on_error=1 \
  out/asan/d8 \
    --allow-natives-syntax \
    --turboshaft \
    --no-maglev \
    --turboshaft-load-elimination \
    --turboshaft-trace-load-elimination \
    --turboshaft-verify-load-elimination \
    --trace-deopt \
    poc_d8.js

Observed result:

>>>> InvalidateAtOffset: not invalidating thanks for maps: MemoryAddress{base=0, index=<invalid OpIndex>, offset=0, elem_size_log2=0, size=4}
>>>> InvalidateAtOffset: invalidating MemoryAddress{base=4, index=<invalid OpIndex>, offset=0, elem_size_log2=0, size=4}
>> Wiping all maps

> ProcessLoad(93)
>> Found potential replacement at offset 72
>>> Confirming replacement
...
[before] {"x":1337}
abort: Turboshaft's load elimination wrongly eliminated a Load
  1. Optional confirmation without the verifier:
out/asan/d8 \
  --allow-natives-syntax \
  --turboshaft \
  --no-maglev \
  --turboshaft-load-elimination \
  --trace-deopt \
  poc_d8.js

Observed result:

[test] 1142 status=41
[before] {"x":1337}
[attack] 2716 status=41
[after] {"x":1337,"y":42,"z":99}

There is no wrong map deopt for the aliased [attack] call in this run, whereas safe variants deopt at the post-transition access.

Type of crash: Renderer/V8 JIT verifier abort in optimized Turboshaft code.

Crash State:

abort: Turboshaft's load elimination wrongly eliminated a Load

==== JS stack trace =========================================

    0: ExitFrame
    1: victim [.../poc_d8.js:~6]
    2: /* anonymous */ [.../poc_d8.js:31]

Relevant LLE trace excerpt:

ProcessCall(40)
  InvalidateMaybeAliasing
  Invalidating MemoryAddress{base=0, offset=12}
  Invalidating MemoryAddress{base=0, offset=0}

ProcessLoad(59)
ProcessAssumeMap(63)        // y has B
ProcessLoad(72)             // bare post-call LoadMapField(x), no AssumeMap(x,B)

ProcessStore(89)            // y.z map transition B -> C
  InvalidateAtOffset: not invalidating thanks for maps: MemoryAddress{base=0, offset=0}
  InvalidateAtOffset: invalidating MemoryAddress{base=4, offset=0}
  Wiping all maps            // too late

ProcessLoad(93)
  Found potential replacement at offset 72
  Confirming replacement

SECURITY IMPACT

The PoC proves that optimized code can accept a stale object map after an aliased map transition and bypass the WrongMap deoptimization guard. The attached named-property harness uses a layout where the read remains benign, so the verifier is the deterministic signal. The same stale-CheckMaps primitive applied to elements-kind or layout-specialized accesses can produce type confusion (for example, double-elements code operating after an object-elements transition). The V8 sandbox does not make stale map-specialized optimized code semantically safe; it only constrains follow-on exploitation primitives.

RECOMMENDED FIX

For map-offset stores, clear map facts before any invalidation routine can consult them:

const bool is_map_store =
    store.offset == HeapObject::kMapOffset && !store.index().valid();
if (is_map_store) {
  WipeAllMaps();
}
if (!invalidate_maybe_aliasing) memory_.Invalidate(store);
memory_.Insert(store);

Also consider clearing map facts across arbitrary effectful calls:

WipeAllMaps();
memory_.InvalidateMaybeAliasing();

If the call-site precision cost is too high, add a dedicated map-store invalidation mode that never relies on object_maps_ to preserve active map-offset loads.

CREDIT INFORMATION

Reporter credit: Mufeed VH from Winfunc Research (winfunc.com)

View on issue tracker
Links in the report