Feb 15, 2026·6 min read·6 visits
Critical Race Condition in Chrome's V8 engine involving `ReadableStream` and `SharedArrayBuffer`. Allows remote attackers to bypass the V8 Sandbox and achieve RCE via a crafted HTML page. Patched in Chrome 139.0.7258.127.
A high-severity race condition in Google Chrome's V8 JavaScript engine allows attackers to bypass the V8 Sandbox and execute arbitrary code. By exploiting the interaction between `ReadableStream` consumers and `SharedArrayBuffer`, attackers can corrupt heap memory via a data race, effectively turning a browser tab into a foothold for system compromise. This vulnerability was successfully demonstrated in v8CTF by researcher Seunghyun Lee.
JavaScript is famously single-threaded. It’s the one thing that keeps browser exploit development somewhat sane—you generally know the state of the world when your code executes. But then came SharedArrayBuffer (SAB). SABs are the chaotic evil of the JavaScript world because they allow true concurrency. They let the main thread and a Web Worker shout at the same chunk of memory simultaneously.
Now, enter ReadableStream. Streams are designed to pipe data efficiently, often optimized in C++ within the V8 engine. V8 developers assume that when they are reading a buffer, the floor won't drop out from under them. They assume stability.
CVE-2025-8880 is what happens when that assumption meets reality. It’s a race condition where the engine tries to consume a stream backed by shared memory, but a concurrent thread modifies that memory state mid-consumption. This isn't just a crash; it's a high-precision tool to break the V8 Sandbox, the security boundary Google built specifically to stop us from doing exactly this.
The vulnerability (CWE-362) lies in the synchronization—or lack thereof—between the ReadableStream implementation and the underlying buffer storage when that storage is a SharedArrayBuffer.
In a typical Time-of-Check to Time-of-Use (TOCTOU) scenario, the engine checks the length of the data to ensure it's safe to read. It says, "Okay, this buffer is 1024 bytes. I will read 1024 bytes." It then proceeds to the read operation.
However, because the backing store is shared, a malicious Web Worker running in the background can modify the buffer or detach it in the nanoseconds between the check and the use. If the worker shrinks the buffer or creates an inconsistent state exactly when the stream consumer (the C++ side) is processing it, V8 ends up reading or writing to memory that no longer belongs to that buffer. This results in memory corruption on the V8 heap.
To understand the fix, we have to look at what was missing. The patch series (CL 6787532, 6811146) introduces memory barriers and explicit locking mechanisms during stream consumption.
The vulnerable logic looked something like this (pseudocode representation of V8 internal logic):
// VULNERABLE LOGIC
void ReadableStream::PullFromSource() {
// 1. Get pointer to buffer
void* data = buffer->data();
// 2. Get length
size_t len = buffer->byte_length();
// ... minimal context switch ...
// 3. Read data assuming 'len' is valid for 'data'
memcpy(destination, data, len);
}If a Web Worker thread resizes or detaches the buffer between step 2 and step 3, len is now stale, and memcpy goes out of bounds. The fix involves ensuring that the buffer cannot be modified while the stream is locking it for a read operation, effectively serializing the access.
> [!NOTE]
> The patch introduces v8::internal::SharedMutex usage in streams.cc to ensure that ReadableStream operations hold a read lock on the backing store, preventing the SAB from mutating under its feet.
This is where Seunghyun Lee (@0x10n) earned his paycheck. Exploiting a race condition in a browser is technically demanding because you have to win the race consistently. The exploit strategy likely involves the following chain:
SharedArrayBuffer and wrap it in a ReadableStream.Response object or a TextDecoder) that will read from the stream.By carefully arranging the heap (Heap Feng Shui), the attacker places a victim object (like an Array or an Object) immediately after the SAB. The out-of-bounds write overwrites the map or elements pointer of the victim object.
Once you have corrupted an object's pointer, you can create a "fake object" primitive. In modern V8, this allows you to read/write anywhere inside the V8 Sandbox. However, because this specific bug corrupts the underlying backing store management, it provides a bridge to write outside the sandbox, leading to full RCE.
Why is this critical? Modern browsers use a technique called the "V8 Sandbox" to contain exploits. Even if you get code execution in JavaScript, you are supposed to be trapped in a 4GB virtual address space, unable to touch the rest of the renderer process.
CVE-2025-8880 is a Sandbox Escape. It breaks the containment vessel. An attacker who exploits this can:
The fact that this was demonstrated in v8CTF proves it is weaponizable. It's not theoretical.
Google patched this in Chrome version 139.0.7258.127. If you are running anything older, your browser is a ticking time bomb waiting for a malicious ad or a compromised website.
Remediation Steps:
chrome://settings/help and ensure you are on the latest stable channel.SharedArrayBuffer usage via the SharedArrayBufferEnabled enterprise policy, though this will break sites like Google Earth, Figma, and heavy web games.For Developers:
This is a stark reminder that SharedArrayBuffer introduces hard concurrency problems into a language that wasn't originally designed for them. If you are writing native modules or working on JS engines, never trust the length of a shared buffer to stay constant across a function call.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
Google Chrome Google | < 139.0.7258.127 | 139.0.7258.127 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-362 (Race Condition) |
| CVSS v3.1 | 8.8 (High) |
| Attack Vector | Network (Web) |
| Privileges Required | None |
| User Interaction | Required (Visit Page) |
| Impact | Remote Code Execution / Sandbox Escape |
Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition')