CVE-2026-79216
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
forthird_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
Patch
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)) {
Original Bug Report
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:
- A Proxy’s
ownKeystrap returns one key, so Blink reserves onepair<String, String>. - During the first
getcallback, 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. - The native loop observes the new length, while the native
Vectorstill has capacity one. - Up to 96 distinct pairs are written through
UncheckedAppend()into and beyond that one-element allocation. - The PoC grooms adjacent one-pair
URLSearchParamsvectors 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
- http://127.0.0.1:8000/poc.html
- https://chromium.googlesource.com/chromium/src/+/81891e5ca708047763816c778216799ef14c66cb/third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h#1142
- https://chromium.googlesource.com/chromium/src/+/81891e5ca708047763816c778216799ef14c66cb/third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h#1246
- https://chromium.googlesource.com/chromium/src/+/81891e5ca708047763816c778216799ef14c66cb/third_party/blink/renderer/core/url/url_search_params.idl#10
- https://chromium.googlesource.com/chromium/src/+/refs/tags/150.0.7871.128/third_party/blink/renderer/platform/wtf/container_annotations.h#13
- https://chromium.googlesource.com/chromium/src/+/refs/tags/150.0.7871.128/third_party/blink/renderer/platform/wtf/vector.h#2544
- https://chromium.googlesource.com/v8/v8/+/2b2f69158528fdd9d86b778cfcc2d0a1c4f8c59f/src/sandbox/README.md