CVE-2026-11068
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifnet/websockets/websocket_basic_stream_adapters.cc |
modified |
Files Changed
net/websockets/websocket_basic_stream_adapters.cc
Patch
From 5d8f582ac89fd617c87bde4d0f7607743e39c8e0 Mon Sep 17 00:00:00 2001 From: Andrew Paseltiner <[email protected]> Date: Wed, 08 Apr 2026 04:39:08 -0700 Subject: [PATCH] WebSocket: Fix sequencing-related UAF in WebSocketSpdyStreamAdapter Evaluate the result of CopySavedReadDataIntoBuffer() before moving and running read_callback_ to avoid a Use-After-Free vulnerability due to C++17 sequencing rules. We also move the callback into a local variable for additional safety. Fixed: 499194333 Change-Id: I67e80f5f1f6ffc89b8e0f0af585e5b22e7cea570 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7726993 Auto-Submit: Andrew Paseltiner <[email protected]> Reviewed-by: Adam Rice <[email protected]> Commit-Queue: Adam Rice <[email protected]> Cr-Commit-Position: refs/heads/main@{#1611387} --- diff --git a/net/websockets/websocket_basic_stream_adapters.cc b/net/websockets/websocket_basic_stream_adapters.cc index 8f3fb3e..a1e691d 100644 --- a/net/websockets/websocket_basic_stream_adapters.cc +++ b/net/websockets/websocket_basic_stream_adapters.cc @@ -165,8 +165,12 @@ } read_data_.Enqueue(std::move(buffer)); - if (read_callback_) - std::move(read_callback_).Run(CopySavedReadDataIntoBuffer()); + if (read_callback_) { + // Avoid UAF due to C++17 sequencing rules. See crbug.com/499194333. + auto callback = std::move(read_callback_); + int rv = CopySavedReadDataIntoBuffer(); + std::move(callback).Run(rv); + } } void WebSocketSpdyStreamAdapter::OnDataSent() {
Original Bug Report
Use-After-Free in WebSocketSpdyStreamAdapter via sequence evaluation reentrancy
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports without the security team.
Overview: A potential Use-After-Free vulnerability exists in WebSocketSpdyStreamAdapter due to C++17 evaluation order sequencing combined with reentrant stream destruction. An attacker can manipulate a HTTP/2 WebSocket connection to trigger synchronous adapter deletion during a read operation, bypassing MiraclePtr protection and potentially achieving Remote Code Execution in the Network process.
Affected files:
net/websockets/websocket_basic_stream_adapters.ccnet/spdy/spdy_read_queue.ccnet/spdy/spdy_buffer.ccnet/spdy/spdy_session.ccservices/network/websocket.cc
Estimated timestamp from git blame: 2019-03-11
Summary
A potential Use-After-Free (UAF) vulnerability exists in WebSocketSpdyStreamAdapter. The issue stems from a C++17 sequence evaluation trap during callback execution, coupled with a highly specific reentrant stream teardown sequence. A malicious server can intentionally exhaust the SPDY session’s capped frame queue to trigger synchronous connection drainage during an active read, destroying the adapter while its methods are still on the stack.
Technical Details
In net/websockets/websocket_basic_stream_adapters.cc, the OnDataReceived method executes the following:
if (read_callback_)
std::move(read_callback_).Run(CopySavedReadDataIntoBuffer());
Per C++17 sequencing rules ([expr.call]/8), the postfix-expression (the object the method is called on) is evaluated before the argument expression. Therefore, std::move(read_callback_) is evaluated first, caching the memory address of the read_callback_ member variable as the implicit this pointer for the upcoming Run() call.
Next, the argument CopySavedReadDataIntoBuffer() is evaluated, which initiates a chain of events that can synchronously destroy the adapter:
CopySavedReadDataIntoBuffercallsread_data_.Dequeue(), which extracts data from aSpdyBufferand pops it from thecircular_deque, destroying the buffer.- The
~SpdyBufferdestructor fires, triggeringConsumeHelper(DISCARD), which calls the registeredSpdySession::OnReadBufferConsumedcallback. - This callback increments the session’s receive window. If conditions are met (e.g., >5 seconds elapsed), it calls
SendWindowUpdateFrame, enqueuing aWINDOW_UPDATEframe to the session. WINDOW_UPDATEframes are “capped frames”. If the session’s write queue is already full of capped frames (e.g., due to an attacker-inducedPINGflood),EnqueueSessionWriteaborts and callsDoDrainSessionto close the connection.DoDrainSessioniterates through active streams and callsSpdyStream::OnClose.
MiraclePtr Bypass: Inside SpdyStream::OnClose, the code copies the delegate_ (which is a raw_ptr) to a local stack pointer and then nullifies the raw_ptr before invoking the closure:
Delegate* delegate = delegate_;
delegate_ = nullptr;
if (delegate)
delegate->OnClose(status);
Because the raw_ptr is cleared before the delegate is destroyed, MiraclePtr (BackupRefPtr) drops its quarantine protection on the adapter’s memory.
The stack continues through WebSocketSpdyStreamAdapter::OnClose, which executes the un-consumed read_callback_, tearing down the WebSocketChannel and synchronously freeing the adapter.
Finally, the stack unwinds back to the original std::move(read_callback_).Run(...) call in OnDataReceived. The execution proceeds using the dangling this pointer cached earlier. Since the memory is unprotected, an attacker who has sprayed the heap can control the BindState and its polymorphic_invoke_ function pointer, leading to Remote Code Execution.
Potential Attacker Steps
(Note: These are suggested steps to trigger the flaw; our agent does not have the ability to run active exploit code)
- Establish Connection: Serve a malicious webpage that opens a WebSocket connection (
wss://) over HTTP/2 to an attacker-controlled server. - Stall Output: The server artificially sets its TCP receive window to 0, preventing Chrome from flushing its write queue to the network.
- Queue Saturation: The server sends exactly 10,000 SPDY/HTTP2
PINGframes. Chrome queues 10,000PING ACKframes, perfectly hitting thesession_max_queued_capped_frames_limit. - Wait: The server waits slightly more than 5 seconds (the default
time_to_buffer_small_window_updates_threshold). - Trigger: The server sends a short WebSocket data frame. The processing of this frame destroys the consumed
SpdyBuffer, triggering the window update, exceeding the capped frame limit, causing session drainage, destroying the adapter, and eventually triggering the UAF when the outer callbackRun()is invoked.
Suggested Fix
Break the sequencing trap by evaluating the argument and storing the result in a local variable before moving and running the callback:
// In net/websockets/websocket_basic_stream_adapters.cc: WebSocketSpdyStreamAdapter::OnDataReceived
if (read_callback_) {
int rv = CopySavedReadDataIntoBuffer();
std::move(read_callback_).Run(rv);
}
Evaluated with Chrome root at commit: ff3d2b74fa39431785bd60e51463b08fcc71ee33
Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.