CVE-2025-5280
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
forsrc/compiler/turboshaft/late-load-elimination-reducer.cc |
modified |
Files Changed
src/compiler/turboshaft/late-load-elimination-reducer.ccsrc/compiler/turboshaft/snapshot-table-opindex.hsrc/compiler/turboshaft/store-store-elimination-phase.ccsrc/flags/flag-definitions.htest/mjsunit/turboshaft/regress-417169470-1.jstest/mjsunit/turboshaft/regress-417169470-2.jstest/mjsunit/turboshaft/regress-417169470-3.jstest/mjsunit/turboshaft/regress-417169470-4.js
Patch
From 37d6fa3f39e17bf46d1cdf340e666cbd3ff976b3 Mon Sep 17 00:00:00 2001
From: Darius Mercadier <dmercadier@chromium.org>
Date: Mon, 19 May 2025 14:48:48 +0200
Subject: [PATCH] [turboshaft] Fix map-based alias analysis in Late Load Elimination
Fixed: 417169470
Change-Id: I589f6667ce3b26b07a4dffa707ee78f9d642d409
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/6564404
Reviewed-by: Marja Hölttä <marja@chromium.org>
Commit-Queue: Darius Mercadier <dmercadier@chromium.org>
Cr-Commit-Position: refs/heads/main@{#100349}
---
diff --git a/src/compiler/turboshaft/late-load-elimination-reducer.cc b/src/compiler/turboshaft/late-load-elimination-reducer.cc
index 7b21b84..e70e556 100644
--- a/src/compiler/turboshaft/late-load-elimination-reducer.cc
+++ b/src/compiler/turboshaft/late-load-elimination-reducer.cc
@@ -409,11 +409,17 @@
non_aliasing_objects_.Set(value, false);
}
- // If we just stored a map, invalidate the maps for this base.
+ // If we just stored a map, invalidate all object_maps_.
if (store.offset == HeapObject::kMapOffset && !store.index().valid()) {
- if (object_maps_.HasKeyFor(store.base())) {
- TRACE(">> Wiping map\n");
- object_maps_.Set(store.base(), MapMaskAndOr{});
+ // TODO(dmercadier): can we only do this for objects that are potentially
+ // aliasing with the `base` (based on their maps and the maps of `base`)?
+ // Also, it might be worth to record a new map if this is actually a map
+ // store.
+ // TODO(dmercadier): do this only if `value` is a Constant with kind
+ // kHeapObject, since all map stores should store a known constant maps.
+ TRACE(">> Wiping all maps\n");
+ for (auto it : object_maps_) {
+ object_maps_.Set(it.second, MapMaskAndOr{});
}
}
}
diff --git a/src/compiler/turboshaft/snapshot-table-opindex.h b/src/compiler/turboshaft/snapshot-table-opindex.h
index 733ef19..496065d 100644
--- a/src/compiler/turboshaft/snapshot-table-opindex.h
+++ b/src/compiler/turboshaft/snapshot-table-opindex.h
@@ -60,6 +60,9 @@
return std::nullopt;
}
+ auto begin() { return indices_to_keys_.begin(); }
+ auto end() { return indices_to_keys_.end(); }
+
private:
Key GetOrCreateKey(OpIndex idx) {
auto it = indices_to_keys_.find(idx);
diff --git a/src/compiler/turboshaft/store-store-elimination-phase.cc b/src/compiler/turboshaft/store-store-elimination-phase.cc
index 1204683..ec00650 100644
--- a/src/compiler/turboshaft/store-store-elimination-phase.cc
+++ b/src/compiler/turboshaft/store-store-elimination-phase.cc
@@ -18,6 +18,9 @@
namespace v8::internal::compiler::turboshaft {
void StoreStoreEliminationPhase::Run(PipelineData* data, Zone* temp_zone) {
+ UnparkedScopeIfNeeded unparked_scope(
+ data->broker(), v8_flags.turboshaft_trace_load_elimination);
+
turboshaft::CopyingPhase<
LoopStackCheckElisionReducer, StoreStoreEliminationReducer,
LateLoadEliminationReducer, MachineOptimizationReducer,
diff --git a/src/flags/flag-definitions.h b/src/flags/flag-definitions.h
index cf1e168..cdf3b2b 100644
--- a/src/flags/flag-definitions.h
+++ b/src/flags/flag-definitions.h
@@ -1644,6 +1644,8 @@
"trace emitted Turboshaft instructions")
DEFINE_BOOL_READONLY(turboshaft_trace_intermediate_reductions, false,
"trace intermediate Turboshaft reduction steps")
+DEFINE_BOOL_READONLY(turboshaft_trace_load_elimination, false,
+ "trace Turboshaft's late load elimination")
#endif // DEBUG
DEFINE_BOOL(profile_guided_optimization, true, "profile guided optimization")
diff --git a/test/mjsunit/turboshaft/regress-417169470-1.js b/test/mjsunit/turboshaft/regress-417169470-1.js
new file mode 100644
index 0000000..ad0dd39
--- /dev/null
+++ b/test/mjsunit/turboshaft/regress-417169470-1.js
@@ -0,0 +1,24 @@
+// 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
+
+function foo(a, b, c) {
+ a.x = 42;
+ b.y = 99; // Transitioning store
+ c.x = 17;
+ return a.x;
+}
+
+// Using a constructor so that we don't go out of object when creating the .y
+// property in foo.
+function MyObject() { this.x = 27; }
+let o1 = new MyObject();
+let o2 = new MyObject();
+
+%PrepareFunctionForOptimization(foo);
+assertEquals(17, foo(o1, o1, o1));
+
+%OptimizeFunctionOnNextCall(foo);
+assertEquals(17, foo(o2, o2, o2));
diff --git a/test/mjsunit/turboshaft/regress-417169470-2.js b/test/mjsunit/turboshaft/regress-417169470-2.js
new file mode 100644
index 0000000..f5ec903
--- /dev/null
+++ b/test/mjsunit/turboshaft/regress-417169470-2.js
@@ -0,0 +1,21 @@
+// 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
+
+function foo(a, b, c) {
+ a.x = 42;
+ b.y = 99; // Transitioning store
+ c.x = 17;
+ return a.x;
+}
+
+let o1 = { x : 27 };
+let o2 = { x : 27 };
+
+%PrepareFunctionForOptimization(foo);
+assertEquals(17, foo(o1, o1, o1));
+
+%OptimizeFunctionOnNextCall(foo);
+assertEquals(17, foo(o2, o2, o2));
diff --git a/test/mjsunit/turboshaft/regress-417169470-3.js b/test/mjsunit/turboshaft/regress-417169470-3.js
new file mode 100644
index 0000000..064946c
--- /dev/null
+++ b/test/mjsunit/turboshaft/regress-417169470-3.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
+
+function foo(a, b, c) {
+ a.x = 42;
+ b.y = 99; // Transitioning store
+ c.x = 17;
+ return a.x;
+}
+
+let o1 = { x : 27 };
+// If we add additional properties, we need to add at least 2 so that `b.y = 99`
+// doesn't end up at offset 12, because that's also the offset of .x (which is
+// in-object rather than out-of-object), and because we don't have map
+// information for backing stores in Late load elimination, we'll assume that
+// the store at b.y can alias with the a.x that was previously loaded.
+o1.unused1 = 12;
+o1.unused2 = 12;
+
+let o2 = { x : 27 };
+o2.unused1 = 12;
+o2.unused2 = 12;
+
+%PrepareFunctionForOptimization(foo);
+assertEquals(17, foo(o1, o1, o1));
+
+%OptimizeFunctionOnNextCall(foo);
+assertEquals(17, foo(o2, o2, o2));
diff --git a/test/mjsunit/turboshaft/regress-417169470-4.js b/test/mjsunit/turboshaft/regress-417169470-4.js
new file mode 100644
index 0000000..d632a8f
--- /dev/null
+++ b/test/mjsunit/turboshaft/regress-417169470-4.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
+
+function opt() {
+ const nop = 0;
+ const empty = {};
+ const a = {p1: 42.42};
+
+ function foo(b) {
+ nop;
+
+ a.p4 = 42;
+ b.p2 = 42;
+ b.p5 = empty;
+ a.p6 = 41.414;
+ }
+
Regression Test / PoC
diff --git a/test/mjsunit/turboshaft/regress-417169470-1.js b/test/mjsunit/turboshaft/regress-417169470-1.js
new file mode 100644
index 0000000..ad0dd39
--- /dev/null
+++ b/test/mjsunit/turboshaft/regress-417169470-1.js
@@ -0,0 +1,24 @@
+// 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
+
+function foo(a, b, c) {
+ a.x = 42;
+ b.y = 99; // Transitioning store
+ c.x = 17;
+ return a.x;
+}
+
+// Using a constructor so that we don't go out of object when creating the .y
+// property in foo.
+function MyObject() { this.x = 27; }
+let o1 = new MyObject();
+let o2 = new MyObject();
+
+%PrepareFunctionForOptimization(foo);
+assertEquals(17, foo(o1, o1, o1));
+
+%OptimizeFunctionOnNextCall(foo);
+assertEquals(17, foo(o2, o2, o2));
diff --git a/test/mjsunit/turboshaft/regress-417169470-2.js b/test/mjsunit/turboshaft/regress-417169470-2.js
new file mode 100644
index 0000000..f5ec903
--- /dev/null
+++ b/test/mjsunit/turboshaft/regress-417169470-2.js
@@ -0,0 +1,21 @@
+// 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
+
+function foo(a, b, c) {
+ a.x = 42;
+ b.y = 99; // Transitioning store
+ c.x = 17;
+ return a.x;
+}
+
+let o1 = { x : 27 };
+let o2 = { x : 27 };
+
+%PrepareFunctionForOptimization(foo);
+assertEquals(17, foo(o1, o1, o1));
+
+%OptimizeFunctionOnNextCall(foo);
+assertEquals(17, foo(o2, o2, o2));
diff --git a/test/mjsunit/turboshaft/regress-417169470-3.js b/test/mjsunit/turboshaft/regress-417169470-3.js
new file mode 100644
index 0000000..064946c
--- /dev/null
+++ b/test/mjsunit/turboshaft/regress-417169470-3.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
+
+function foo(a, b, c) {
+ a.x = 42;
+ b.y = 99; // Transitioning store
+ c.x = 17;
+ return a.x;
+}
+
+let o1 = { x : 27 };
+// If we add additional properties, we need to add at least 2 so that `b.y = 99`
+// doesn't end up at offset 12, because that's also the offset of .x (which is
+// in-object rather than out-of-object), and because we don't have map
+// information for backing stores in Late load elimination, we'll assume that
+// the store at b.y can alias with the a.x that was previously loaded.
+o1.unused1 = 12;
+o1.unused2 = 12;
+
+let o2 = { x : 27 };
+o2.unused1 = 12;
+o2.unused2 = 12;
+
+%PrepareFunctionForOptimization(foo);
+assertEquals(17, foo(o1, o1, o1));
+
+%OptimizeFunctionOnNextCall(foo);
+assertEquals(17, foo(o2, o2, o2));
diff --git a/test/mjsunit/turboshaft/regress-417169470-4.js b/test/mjsunit/turboshaft/regress-417169470-4.js
new file mode 100644
index 0000000..d632a8f
--- /dev/null
+++ b/test/mjsunit/turboshaft/regress-417169470-4.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
+
+function opt() {
+ const nop = 0;
+ const empty = {};
+ const a = {p1: 42.42};
+
+ function foo(b) {
+ nop;
+
+ a.p4 = 42;
+ b.p2 = 42;
+ b.p5 = empty;
+ a.p6 = 41.414;
+ }
+
+ a.p3 = 42;
+ a.p4 = 42;
+
+ for (let i = 0; i < 300; i++) {
+ foo(a);
+ }
+}
+
+opt();
+opt();
+opt();
Original Bug Report
V8 Turboshaft Late Load Elimination Aliasing bug leads to Memory Corruption
Steps to reproduce the problem
- ./d8 –expose-externalize-string –omit-quit –allow-natives-syntax –fuzzing –jit-fuzzing –future –harmony –js-staging –wasm-staging –wasm-fast-api –expose-fast-api –turbolev –shared-string-table poc.js
Problem Description
I’ll analyze it and upload it soon.
poc.js
function opt(){
function v3() {
const v4 = {"B":42};
}
const v2 = {};
const v6 = {"C":42.42};
function v10(v11) {
function v13() {
v6.e = 41.414;
for (let v19 = 0; v19 < 100; v19++) {
//const v4 = {"B":42};
v3();
}
}
v6.d = 42;
v11.b = 42;
v11.f = v2;
for (let v26 = 0; v26 < 100; v26++) {
v13();
}
}
v6.c = 42;
v6.d = v2;
for (let v36 = 0; v36 < 1000; v36++) {
v10(v6);
}
}
opt();
opt();
opt();
Summary
V8 Turbolev Memory Corruption
Custom Questions
Crash state:
./d8 –expose-externalize-string –omit-quit –allow-natives-syntax –fuzzing –jit-fuzzing –future –harmony –js-staging –wasm-staging –wasm-fast-api –expose-fast-api –turbolev –shared-string-table test3.js [COV] no shared memory bitmap available, skipping [COV] edge counters initialized. Shared memory: (null) with 1520960 edges V8 is running with experimental features enabled. Stability and security will suffer. Received signal 11 SEGV_ACCERR 7e8a00000014
==== C stack trace ===============================
./d8(___interceptor_backtrace+0x46)[0x5a98942fd856] ./d8(+0x26e5959)[0x5a98947a5959] /lib/x86_64-linux-gnu/libc.so.6(+0x42520)[0x78df3e842520] [0x5a98e0002849] [end of stack trace] Segmentation fault
Reporter credit:
un3xploitable
Additional Data
Category: Security
Chrome Channel: Not sure
Regression: N/A