Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactBuffer overflow in Blink
DescriptionBuffer overflow in Blink
ComponentBlink
Bug ClassOOB
Tracker536913431
Fix commitdf0f26494ab3 (chromium/src) +8/-3
CISA KEVNot listed
CreditedFound by XBOW and triaged by Andrés Luksenberg
Disclosed2026-08-25

Changed Functions

FunctionChangeNotes
for
third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h
modified

Files Changed

  • third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h
From df0f26494ab36387a14c257578be1e5987c5ea61 Mon Sep 17 00:00:00 2001
From: Dave Tapuska <[email protected]>
Date: Thu, 23 Jul 2026 09:15:52 -0700
Subject: [PATCH] Cache the length of keys when converting to IDL record types.

Store the length of the keys array in a local variable before iterating. This ensures the length does not change during the loop, preventing potential issues with UncheckedAppend() if the array is modified during iteration.

BUG=536913431

Change-Id: I7589d1d417c4e8dbcce2a5bcd8b5bd96596f8f8a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8138460
Reviewed-by: Jeremy Roman <[email protected]>
Commit-Queue: Dave Tapuska <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1667167}
---

diff --git a/third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h b/third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h
index bbc4252c..eafc6a5 100644
--- a/third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h
+++ b/third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h
@@ -1174,14 +1174,19 @@
              .ToLocal(&keys)) {
       return ImplType();
     }
-    if (keys->Length() > ImplType::MaxCapacity()) {
+
+    // Store the length because we use UncheckedAppend() below and we want to
+    // make sure that the length does not change during the loop.
+    uint32_t length = keys->Length();
+
+    if (length > ImplType::MaxCapacity()) {
       exception_state.ThrowRangeError("Array length exceeds supported limit.");
       return ImplType();
     }
 
     // "2. Let result be a new empty instance of record<K, V>."
     ImplType result;
-    result.ReserveInitialCapacity(keys->Length());
+    result.ReserveInitialCapacity(length);
 
     // The conversion algorithm needs a data structure with fast insertion at
     // the end while at the same time requiring fast checks for previous insert
@@ -1189,7 +1194,7 @@
     // the latter part.
     HashMap<String, uint32_t> seen_keys;
 
-    for (uint32_t i = 0; i < keys->Length(); ++i) {
+    for (uint32_t i = 0; i < length; ++i) {
       // "4. Repeat, for each element key of keys in List order:"
       v8::Local<v8::Value> key;
       if (!keys->Get(context, i).ToLocal(&key)) {
Loading diff…

Original Bug Report

reported by [email protected]

Blink IDLRecord conversion can outgrow its reserved native Vector during reentrancy

VULNERABILITY DETAILS

Blink’s generic Web IDL record<K,V> conversion reserves a native Vector from a V8 array’s length once, but re-reads the mutable V8 length on every loop iteration and appends with UncheckedAppend(). If an attacker who has compromised the V8 sandbox changes the live key array during a JavaScript callback, the loop can append more native elements than were reserved, causing an out-of-bounds write in PartitionAlloc memory.

The vulnerable conversion is generic Blink C++ with no operating-system or architecture guard, so the capacity violation itself is not Linux-specific. The attached PoC was validated on Linux x86-64. Its classification as a V8 sandbox bypass applies to supported 64-bit V8-sandbox configurations; V8’s sandbox is available only in 64-bit configurations. We did not test the PoC’s heap-layout assumptions on other targets.

The affected implementation is NativeValueTraits<IDLRecord<K,V>>::NativeValue(). The relevant sequence is:

v8::Local<v8::Array> keys;
v8_object->GetOwnPropertyNames(...).ToLocal(&keys);

ImplType result;
result.ReserveInitialCapacity(keys->Length());

for (uint32_t i = 0; i < keys->Length(); ++i) {
  // GetOwnPropertyDescriptor(), property reads, and K/V conversions can call JS.
  ...
  result.UncheckedAppend(
      std::make_pair(std::move(typed_key), std::move(typed_value)));
}

GetOwnPropertyDescriptor(), reads from the descriptor and input object, and key/value conversions are callback-capable. The copied keys array is not directly exposed to the page, but it is inside the V8 sandbox. The stated attacker precondition—arbitrary read/write within that sandbox—allows its elements and length to be changed during one of those callbacks.

The attached PoC uses the public URLSearchParams record overload:

  1. A Proxy’s ownKeys trap returns one key, so Blink reserves one pair<String, String>.
  2. During the first get callback, the PoC uses V8’s official sandbox-testing API to locate Blink’s copied JSArray and replace its elements and length with a prepared 256-key array.
  3. The native loop observes the new length, while the native Vector still has capacity one.
  4. Up to 96 distinct pairs are written through UncheckedAppend() into and beyond that one-element allocation.
  5. The PoC grooms adjacent one-pair URLSearchParams vectors and checks for changed native entries. If it survives the overwrite, it also performs a JavaScript method call that writes back through a corrupted adjacent native vector.

In an instrumented API-enabled Chromium 151.0.7882.0 build at revision 3ddb585438332a1ab87654d620c5e6a488fe7f1b, where this code remained vulnerable, the defining transition was:

initial_keys=1 capacity=1 size=0
after value callback: dynamic_keys=256
append: pos=0 capacity=1
append: pos=1 capacity=1   # first native OOB pair
append: pos=2 capacity=1

This report assumes a compromised V8 sandbox. The Sandbox object in the PoC supplies only that precondition; it is not the vulnerability and is not available in production builds. V8 documents this testing API as a way to emulate common sandbox memory-corruption primitives.

The vulnerability reported here begins when Blink trusts the mutable V8 object after reserving native capacity. Its security impact is a V8 sandbox bypass: controlled writes reach native Blink/PartitionAlloc memory outside the V8 cage.

VERSION

  • Validated Chrome configuration: Stable 150.0.7871.128, Linux x86-64
  • Code-level scope: generic Blink record conversion on all Blink targets; V8-sandbox-bypass impact applies to supported 64-bit sandbox configurations
  • Chromium revision: 81891e5ca708047763816c778216799ef14c66cb
  • V8 revision: 2b2f69158528fdd9d86b778cfcc2d0a1c4f8c59f
  • Chrome GNU build ID: 2f071daf2a0b4aa68bda1729031294d443a989ab
  • OS used for validation: Ubuntu 24.04 x86-64

REPRODUCTION CASE

Attachment: poc.html

The focused testcase intentionally uses V8’s testing API to provide the documented compromised-sandbox precondition while isolating the Blink bug. Build at the affected revision with the API enabled. This must be a non-ASan build because Blink changes the behavior of UncheckedAppend() in its Linux x86-64 ASan configuration, as explained under the crash information below:

gn gen out/idlrecord-poc --args='is_asan=false is_debug=false dcheck_always_on=false symbol_level=1 blink_symbol_level=1 v8_symbol_level=1 v8_enable_sandbox=true v8_enable_memory_corruption_api=true'
autoninja -C out/idlrecord-poc chrome

Then:

cd idlrecord
python3 -m http.server 8000

/path/to/chromium/src/out/idlrecord-poc/chrome \
  --user-data-dir=/tmp/chrome-idlrecord-poc \
  --js-flags=--sandbox-testing \
  http://127.0.0.1:8000/poc.html

Expected behavior is a renderer crash/hang after the first append beyond capacity or a JSON result with "ok": true, changed adjacent URLSearchParams entries, and a writeBack object showing a JavaScript-driven native slot update. The exact outcome depends on the adjacent PartitionAlloc layout.

FOR CRASHES, PLEASE INCLUDE THE FOLLOWING ADDITIONAL INFORMATION

Type of crash: renderer process (native Blink heap corruption).

Crash State: No ASan trace is available for the validated Linux x86-64 target because M150’s standard ASan configuration changes the vulnerable operation into a checked one. container_annotations.h defines ANNOTATE_CONTIGUOUS_CONTAINER for Linux/ChromeOS x86-64 ASan builds. Under that macro, the M150 Vector::UncheckedAppend() implementation calls push_back(), which grows the vector when it is full, instead of performing the unchecked placement used in release builds. Producing an ASan report would therefore require altering Blink’s ASan-specific implementation and would not reproduce the shipped vulnerable operation under Chrome’s standard ASan configuration.

In a non-ASan build, the exact first invalid write is deterministic at the source level: after capacity one is reserved, result.UncheckedAppend() at position one writes a second pair<String, String> immediately beyond the allocation. On the validated x86-64 build the pair is 16 bytes; its size is target-dependent. Subsequent positions extend the overwrite.

The final fault location is heap-layout-dependent because the unchecked loop can corrupt several adjacent native objects before one is dereferenced. For triage, the key diagnostic is the allocation stack for ReserveInitialCapacity(1) paired with the write stack at UncheckedAppend().

CREDIT INFORMATION

Reporter credit: Found by XBOW and triaged by Andrés Luksenberg

View on issue tracker