Chrome · V8
CVE-2026-5893
Race in V8
Overview
Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifsrc/objects/elements.cc |
modified | |
forsrc/objects/elements.cc |
modified |
Files Changed
src/objects/elements.ccsrc/objects/elements.hsrc/objects/js-objects.cc
Patch
From 02cd73dfc58dbe73054382c85f9f126af9ce597e Mon Sep 17 00:00:00 2001 From: Marja Hölttä <[email protected]> Date: Mon, 02 Mar 2026 20:05:25 +0100 Subject: [PATCH] [RAB/GSAB] Object.values: Handle a TA grown by a background thread gracefully Fixed: 487768771 Change-Id: I49dada228d49f3a36d083d0b1e009112bfeea89a Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7623620 Reviewed-by: Igor Sheludko <[email protected]> Commit-Queue: Marja Hölttä <[email protected]> Cr-Commit-Position: refs/heads/main@{#105548} --- diff --git a/src/objects/elements.cc b/src/objects/elements.cc index 3be69a6..351a76c 100644 --- a/src/objects/elements.cc +++ b/src/objects/elements.cc @@ -1281,16 +1281,20 @@ Maybe<bool> CollectValuesOrEntries(Isolate* isolate, DirectHandle<JSObject> object, DirectHandle<FixedArray> values_or_entries, - bool get_entries, uint32_t* nof_items, + uint32_t max_nof_items, bool get_entries, + uint32_t* nof_items, PropertyFilter filter) override { - return Subclass::CollectValuesOrEntriesImpl( - isolate, object, values_or_entries, get_entries, nof_items, filter); + auto to_return = Subclass::CollectValuesOrEntriesImpl( + isolate, object, values_or_entries, max_nof_items, get_entries, + nof_items, filter); + CHECK_LE(*nof_items, max_nof_items); + return to_return; } static Maybe<bool> CollectValuesOrEntriesImpl( Isolate* isolate, DirectHandle<JSObject> object, - DirectHandle<FixedArray> values_or_entries, bool get_entries, - uint32_t* nof_items, PropertyFilter filter) { + DirectHandle<FixedArray> values_or_entries, uint32_t max_nof_items, + bool get_entries, uint32_t* nof_items, PropertyFilter filter) { DCHECK_EQ(*nof_items, 0); KeyAccumulator accumulator(isolate, KeyCollectionMode::kOwnOnly, ALL_PROPERTIES); @@ -2881,8 +2885,8 @@ static Maybe<bool> CollectValuesOrEntriesImpl( Isolate* isolate, DirectHandle<JSObject> object, - DirectHandle<FixedArray> values_or_entries, bool get_entries, - uint32_t* nof_items, PropertyFilter filter) { + DirectHandle<FixedArray> values_or_entries, uint32_t max_nof_items, + bool get_entries, uint32_t* nof_items, PropertyFilter filter) { uint32_t count = 0; if (get_entries) { // Collecting entries needs to allocate, so this code must be handlified. @@ -3377,8 +3381,8 @@ static Maybe<bool> CollectValuesOrEntriesImpl( Isolate* isolate, DirectHandle<JSObject> object, - DirectHandle<FixedArray> values_or_entries, bool get_entries, - uint32_t* nof_items, PropertyFilter filter) { + DirectHandle<FixedArray> values_or_entries, uint32_t max_nof_items, + bool get_entries, uint32_t* nof_items, PropertyFilter filter) { DirectHandle<FixedDoubleArray> elements( Cast<FixedDoubleArray>(object->elements()), isolate); uint32_t count = 0; @@ -3717,12 +3721,17 @@ static Maybe<bool> CollectValuesOrEntriesImpl( Isolate* isolate, DirectHandle<JSObject> object, - DirectHandle<FixedArray> values_or_entries, bool get_entries, - uint32_t* nof_items, PropertyFilter filter) { + DirectHandle<FixedArray> values_or_entries, uint32_t max_nof_items, + bool get_entries, uint32_t* nof_items, PropertyFilter filter) { uint32_t count = 0; if ((filter & ONLY_CONFIGURABLE) == 0) { DirectHandle<FixedArrayBase> elements(object->elements(), isolate); size_t length = AccessorClass::GetCapacityImpl(*object, *elements); + // The TypedArray might have been grown by a background thread. Handle it + // gracefully. + if (length > max_nof_items) { + length = max_nof_items; + } for (size_t index = 0; index < length; ++index) { DirectHandle<Object> value = AccessorClass::GetInternalImpl( isolate, object, InternalIndex(index)); diff --git a/src/objects/elements.h b/src/objects/elements.h index f763961..c11363e 100644 --- a/src/objects/elements.h +++ b/src/objects/elements.h @@ -92,8 +92,9 @@ virtual Maybe<bool> CollectValuesOrEntries( Isolate* isolate, DirectHandle<JSObject> object, - DirectHandle<FixedArray> values_or_entries, bool get_entries, - uint32_t* nof_items, PropertyFilter filter = ALL_PROPERTIES) = 0; + DirectHandle<FixedArray> values_or_entries, uint32_t max_nof_items, + bool get_entries, uint32_t* nof_items, + PropertyFilter filter = ALL_PROPERTIES) = 0; virtual MaybeHandle<FixedArray> PrependElementIndices( Isolate* isolate, DirectHandle<JSObject> object, diff --git a/src/objects/js-objects.cc b/src/objects/js-objects.cc index aa69662..8f1ac7e 100644 --- a/src/objects/js-objects.cc +++ b/src/objects/js-objects.cc @@ -2257,8 +2257,9 @@ if (object->elements() != ReadOnlyRoots(isolate).empty_fixed_array()) { MAYBE_RETURN(object->GetElementsAccessor()->CollectValuesOrEntries( - isolate, object, values_or_entries, get_entries, &count, - ENUMERABLE_STRINGS), + isolate, object, values_or_entries, + static_cast<uint32_t>(number_of_own_elements), get_entries, + &count, ENUMERABLE_STRINGS), Nothing<bool>()); }
Loading diff…
Original Bug Report
reported by [email protected]
Debug check failed: IsInBounds(index).
Steps to reproduce the problem
run with: Download worker.js and poc.js and put them in the same directory. d8 poc.js
Problem Description
Root Cause
The root cause is inconsistent length snapshots within one logical operation, plus missing write-side capacity bounds.
- Allocation uses old length (snapshot A)
- Writing re-reads a new length (snapshot B, possibly larger)
- When
snapshot B > snapshot A,values_or_entries->set(count++, ...)writes out of bounds
This is a classic shared-memory TOCTOU problem under GSAB + length-tracking TypedArray concurrent growth.
Trigger Path and Call Chain
Key call chain:
Object.values(ta)/Object.entries(ta)enters the fast path.FastGetOwnValuesOrEntriesallocates the result array using one length snapshot:src/objects/js-objects.cc:2240src/objects/js-objects.cc:2249
- Then it enters TypedArray element collection:
src/objects/js-objects.cc:2254src/objects/elements.cc:3719
TypedElementsAccessor::CollectValuesOrEntriesImplreads the current length again and writes in a loop:- Length read:
src/objects/elements.cc:3726 - Write site:
src/objects/elements.cc:3733
- Length read:
In the PoC, a worker concurrently executes sab.grow(...), making step-2 allocation length smaller than step-4 write length, which causes OOB writes.
Additional Comments
Introduced by commit
commit 3160edf011b11347dd741c1c09a7fcb57bb479c4
[rab/gsab] ResizableArrayBuffer / GrowableSharedArrayBuffer part 1
Detailed list of changes:
https://docs.google.com/document/d/15i4-SZDzFDW7FfclIYuZEhFn-q-KpobCBy23x9zZZLc/edit?usp=sharing
Bug: v8:11111
Change-Id: I931003bd4552cf91d57de95af04a427a9e6d6ac9
Summary
Debug check failed: IsInBounds(index).
Custom Questions
Type of crash:
tab
Crash state:
# Fatal error in ../../src/objects/fixed-array-inl.h, line 160
# Debug check failed: IsInBounds(index).
#
#
#
#FailureMessage Object: 0x7ffcc5f29308
==== C stack trace ===============================
/home/qy/new2/v8/out/x64.debug/libv8_libbase.so(v8::base::debug::StackTrace::StackTrace()+0x29) [0x7d3f31b090e9]
/home/qy/new2/v8/out/x64.debug/libv8_libplatform.so(+0x4e29d) [0x7d3f31a6a29d]
/home/qy/new2/v8/out/x64.debug/libv8_libbase.so(V8_Fatal(char const*, int, char const*, ...)+0x205) [0x7d3f31add2f5]
/home/qy/new2/v8/out/x64.debug/libv8_libbase.so(+0x53b8c) [0x7d3f31adcb8c]
/home/qy/new2/v8/out/x64.debug/libv8_libbase.so(V8_Dcheck(char const*, int, char const*)+0x4d) [0x7d3f31add3ed]
/home/qy/new2/v8/out/x64.debug/libv8.so(v8::internal::TaggedArrayBase<v8::internal::FixedArray, v8::internal::TaggedArrayShape, v8::internal::HeapObjectLayout>::set(unsigned int, v8::internal::Tagged<v8::internal::Object>, v8::internal::WriteBarrierMode)+0x81) [0x7d3f2bbb9fe1]
/home/qy/new2/v8/out/x64.debug/libv8.so(+0xa852a36) [0x7d3f2cc52a36]
/home/qy/new2/v8/out/x64.debug/libv8.so(+0xa8508cb) [0x7d3f2cc508cb]
/home/qy/new2/v8/out/x64.debug/libv8.so(v8::internal::FastGetOwnValuesOrEntries(v8::internal::Isolate*, v8::internal::DirectHandle<v8::internal::JSReceiver>, bool, v8::internal::Handle<v8::internal::FixedArray>*)+0x5f1) [0x7d3f2cdcc7f1]
/home/qy/new2/v8/out/x64.debug/libv8.so(v8::internal::GetOwnValuesOrEntries(v8::internal::Isolate*, v8::internal::DirectHandle<v8::internal::JSReceiver>, v8::internal::PropertyFilter, bool, bool)+0x8b) [0x7d3f2cdcd89b]
/home/qy/new2/v8/out/x64.debug/libv8.so(v8::internal::JSReceiver::GetOwnValues(v8::internal::Isolate*, v8::internal::DirectHandle<v8::internal::JSReceiver>, v8::internal::PropertyFilter, bool)+0x4a) [0x7d3f2cdce0ba]
/home/qy/new2/v8/out/x64.debug/libv8.so(+0xaf3b29d) [0x7d3f2d33b29d]
/home/qy/new2/v8/out/x64.debug/libv8.so(v8::internal::Runtime_ObjectValues(int, unsigned long*, v8::internal::Isolate*)+0x151) [0x7d3f2d33af21]
/home/qy/new2/v8/out/x64.debug/libv8.so(+0x8c289bd) [0x7d3f2b0289bd]
Trace/breakpoint trap (core dumped)
Reporter credit:
QYmag1c
Additional Data
Category: Security
Chrome Channel: Not sure
Regression: N/A \
References
On This Page