Chrome · V8
CVE-2026-19556
UAF in V8
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
fortest/mjsunit/regress/regress-535000102.js |
modified |
Files Changed
src/objects/string.tqtest/mjsunit/mjsunit.statustest/mjsunit/regress/regress-535000102.js
Patch
From 383120c3acd389f21cae65add09b75230ee929d7 Mon Sep 17 00:00:00 2001 From: Jakob Linke <[email protected]> Date: Thu, 30 Jul 2026 15:39:51 +0200 Subject: [PATCH] [string] Fix UAF in TwoStringsToSlices with external strings TwoStringsToSlices converted s1 to a slice before converting s2. For an external s1 that slice is a raw pointer into the resource with no tagged owner (NewOffHeapConstSlice stores kZeroBitPattern), so it keeps nothing alive. Converting s2 can flatten it, and the allocation in StringSlowFlatten can collect s1 and dispose its resource, leaving the slice dangling for the subsequent search. Flatten both strings up front instead. Afterwards StringToSlice cannot allocate: its ConsString arm is the only allocating one, and the SlicedString/ThinString arms walk to parents that are already direct. Fixed: 535000102 Change-Id: Ic438a73fa3ae9f1fad36d4db01d298cc3252b595 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/8176583 Auto-Submit: Jakob Linke <[email protected]> Reviewed-by: Leszek Swirski <[email protected]> Commit-Queue: Jakob Linke <[email protected]> Cr-Commit-Position: refs/heads/main@{#108978} --- diff --git a/src/objects/string.tq b/src/objects/string.tq index a506d11..2ac2c80 100644 --- a/src/objects/string.tq +++ b/src/objects/string.tq @@ -330,6 +330,11 @@ // Dispatch on the slice type of two different strings. macro TwoStringsToSlices<Result: type, Functor: type>( s1: String, s2: String, f: Functor): Result { + // Flatten both strings before taking either slice. A slice into an external + // string is a raw pointer that doesn't keep the string alive, so flattening + // s2 while holding s1's slice could collect s1 and free its resource. + const s1 = Flatten(s1); + const s2 = Flatten(s2); try { StringToSlice(s1) otherwise FirstOneByte, FirstTwoByte; } label FirstOneByte(s1Slice: ConstSlice<char8>) { diff --git a/test/mjsunit/mjsunit.status b/test/mjsunit/mjsunit.status index b68df3c..0e1d1ab 100644 --- a/test/mjsunit/mjsunit.status +++ b/test/mjsunit/mjsunit.status @@ -2757,6 +2757,7 @@ 'regress/wasm/regress-483269968': [SKIP], 'compiler/regress-347724915': [SKIP], 'regress/regress-crbug-418520151': [SKIP], + 'regress/regress-535000102': [SKIP], # TODO(v8:14581): Re-enable this test once TransitionElementsKindOrCheckMap is # supported by the Maglev->Turboshaft graph builder. diff --git a/test/mjsunit/regress/regress-535000102.js b/test/mjsunit/regress/regress-535000102.js new file mode 100644 index 0000000..b7584bc --- /dev/null +++ b/test/mjsunit/regress/regress-535000102.js @@ -0,0 +1,43 @@ +// Copyright 2026 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 --expose-gc --expose-externalize-string +// Flags: --gc-global + +// String.prototype.indexOf takes a slice of the receiver before flattening the +// search string. A slice into an external string is a raw pointer that doesn't +// keep the string alive, so the allocation done while flattening the search +// string could collect the receiver and free its resource. +// +// External strings are only disposed by major GCs, so the flattening allocation +// has to hit one. --gc-global just makes that deterministic; the bug also +// reproduces under ordinary old space pressure. + +let subject = null; + +function makeExternal(s) { + const str = createExternalizableString(s); + externalizeString(str, true); + return str; +} + +function indexOfAndDropSubject(search) { + const s = subject; + subject = null; + // Make the allocation that flattens {search} trigger a GC. + %SimulateNewspaceFull(); + return s.indexOf(search); +} + +%PrepareFunctionForOptimization(indexOfAndDropSubject); +for (let i = 0; i < 3; i++) { + subject = makeExternal('a'.repeat(64)); + indexOfAndDropSubject('b' + 'c'); +} +%OptimizeFunctionOnNextCall(indexOfAndDropSubject); + +subject = makeExternal('A'.repeat(20 * 1024)); +// A cons string, so that indexOf has to flatten it. +const search = 'B'.repeat(1024) + 'C'.repeat(1024); +assertEquals(-1, indexOfAndDropSubject(search));
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/test/mjsunit/mjsunit.status b/test/mjsunit/mjsunit.status
index b68df3c..0e1d1ab 100644
--- a/test/mjsunit/mjsunit.status
+++ b/test/mjsunit/mjsunit.status
@@ -2757,6 +2757,7 @@
'regress/wasm/regress-483269968': [SKIP],
'compiler/regress-347724915': [SKIP],
'regress/regress-crbug-418520151': [SKIP],
+ 'regress/regress-535000102': [SKIP],
# TODO(v8:14581): Re-enable this test once TransitionElementsKindOrCheckMap is
# supported by the Maglev->Turboshaft graph builder.
diff --git a/test/mjsunit/regress/regress-535000102.js b/test/mjsunit/regress/regress-535000102.js
new file mode 100644
index 0000000..b7584bc
--- /dev/null
+++ b/test/mjsunit/regress/regress-535000102.js
@@ -0,0 +1,43 @@
+// Copyright 2026 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 --expose-gc --expose-externalize-string
+// Flags: --gc-global
+
+// String.prototype.indexOf takes a slice of the receiver before flattening the
+// search string. A slice into an external string is a raw pointer that doesn't
+// keep the string alive, so the allocation done while flattening the search
+// string could collect the receiver and free its resource.
+//
+// External strings are only disposed by major GCs, so the flattening allocation
+// has to hit one. --gc-global just makes that deterministic; the bug also
+// reproduces under ordinary old space pressure.
+
+let subject = null;
+
+function makeExternal(s) {
+ const str = createExternalizableString(s);
+ externalizeString(str, true);
+ return str;
+}
+
+function indexOfAndDropSubject(search) {
+ const s = subject;
+ subject = null;
+ // Make the allocation that flattens {search} trigger a GC.
+ %SimulateNewspaceFull();
+ return s.indexOf(search);
+}
+
+%PrepareFunctionForOptimization(indexOfAndDropSubject);
+for (let i = 0; i < 3; i++) {
+ subject = makeExternal('a'.repeat(64));
+ indexOfAndDropSubject('b' + 'c');
+}
+%OptimizeFunctionOnNextCall(indexOfAndDropSubject);
+
+subject = makeExternal('A'.repeat(20 * 1024));
+// A cons string, so that indexOf has to flatten it.
+const search = 'B'.repeat(1024) + 'C'.repeat(1024);
+assertEquals(-1, indexOfAndDropSubject(search));
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page