Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in QUIC
DescriptionUse after free in QUIC
ComponentQUIC
Bug ClassUAF
Tracker499252371
Fix commit37ff938a93cb (openscreen) +0/-6
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-30

Files Changed

  • osp/impl/quic/open_screen_session_base.cc
  • osp/impl/quic/open_screen_session_base.h
From 37ff938a93cb04c6b77e019b52328c8e9b320317 Mon Sep 17 00:00:00 2001
From: Adam Rice <ricea@chromium.org>
Date: Fri, 22 May 2026 14:19:46 +0900
Subject: [PATCH] Remove deprecated CreateIncomingStream method

This patch removes the `CreateIncomingStream(quic::PendingStream*)`
overload from `OpenScreenSessionBase`. This method is deprecated and was
unused, with its implementation only triggering an unreachable code
assertion (`OSP_NOTREACHED()`). Removing it cleans up the class
interface and eliminates dead code. It also makes Open Screen compatible
with newer versions of QUICHE that do not have the method in their base
class.

Bug: 499252371
Change-Id: Ic046cd0b7e83018f3dbff42e5402619eb6e8d705
Reviewed-on: https://chromium-review.googlesource.com/c/openscreen/+/7870270
Reviewed-by: Muyao Xu <muyaoxu@google.com>
Commit-Queue: Adam Rice <ricea@chromium.org>
---

diff --git a/osp/impl/quic/open_screen_session_base.cc b/osp/impl/quic/open_screen_session_base.cc
index d8ebad0..ad79822 100644
--- a/osp/impl/quic/open_screen_session_base.cc
+++ b/osp/impl/quic/open_screen_session_base.cc
@@ -89,11 +89,6 @@
   return stream_ptr;
 }
 
-quic::QuicStream* OpenScreenSessionBase::CreateIncomingStream(
-    quic::PendingStream* /*pending*/) {
-  OSP_NOTREACHED();
-}
-
 bool OpenScreenSessionBase::ShouldKeepConnectionAlive() const {
   // OpenScreen connections stay alive until they're explicitly closed.
   return true;
diff --git a/osp/impl/quic/open_screen_session_base.h b/osp/impl/quic/open_screen_session_base.h
index 9b00034..7b3c270 100644
--- a/osp/impl/quic/open_screen_session_base.h
+++ b/osp/impl/quic/open_screen_session_base.h
@@ -74,7 +74,6 @@
     return crypto_stream_.get();
   }
   quic::QuicStream* CreateIncomingStream(quic::QuicStreamId id) override;
-  quic::QuicStream* CreateIncomingStream(quic::PendingStream* pending) override;
   bool ShouldKeepConnectionAlive() const override;
 
   std::unique_ptr<quic::QuicConnection> connection_;
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential Use-After-Free in QuicUnackedPacketMap via Re-entrant Packet Flush

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: When processing a QUIC packet containing a DATAGRAM frame, QuicUnackedPacketMap::NotifyFramesAcked disables safe frame copying and reference refreshing. If a co-bundled frame (like MAX_STREAMS) triggers a synchronous packet send, the underlying deque can relocate, leaving a dangling pointer. An attacker can leverage this to achieve an unmitigated arbitrary delete primitive and potential RCE in the Network Service.

Affected files:

  • net/third_party/quiche/src/quiche/quic/core/quic_unacked_packet_map.cc
  • net/third_party/quiche/src/quiche/quic/core/quic_sent_packet_manager.cc
  • net/third_party/quiche/src/quiche/quic/core/quic_transmission_info.h
  • net/third_party/quiche/src/quiche/quic/core/quic_session.cc

Estimated timestamp from git blame: 2025-08-28

Background

When a sent QUIC packet is acknowledged, QuicUnackedPacketMap::NotifyFramesAcked is invoked to process its frames. It receives a reference QuicTransmissionInfo*& info pointing to the packet’s metadata within a QuicheCircularDeque.

Because frame processing can trigger re-entrant packet sends (which push new elements to the deque and might trigger a reallocation of the deque’s backing array), the function typically copies the frames and uses an absl::Cleanup block to refresh the info pointer after the loop.

Vulnerability Details

There is a logic flaw in NotifyFramesAcked that explicitly disables these defensive mechanisms if the packet contains a DATAGRAM_FRAME:

const bool use_copied_frames = !HasDatagramFrame(info->retransmittable_frames);
if (use_copied_frames) {
  // ... copies frames ...
}
absl::Cleanup cleanup = [&]() {
  if (use_copied_frames) { // <-- info is NOT refreshed if DATAGRAM is present
    info = GetMutableTransmissionInfo(packet_number);
  }
};

This creates a severe Use-After-Free (UAF) condition. If an attacker can force a re-entrant packet send while processing a packet containing both a DATAGRAM_FRAME and another frame (such as MAX_STREAMS_FRAME), the QuicheCircularDeque may relocate. The info pointer will become a dangling pointer to the freed backing array.

When NotifyFramesAcked returns, the caller (QuicSentPacketManager::MarkPacketHandled) continues to use this dangling info pointer, eventually calling unacked_packets_.RemoveRetransmittability(info). This method invokes DeleteFrames(&info->retransmittable_frames) on the freed memory.

Potential Exploitation Steps

Note: These are potential steps an attacker might use, as we do not yet have a working executable PoC.

  1. Establish Connection: The attacker hosts a malicious server and the victim connects via WebTransport (enabling DATAGRAM_FRAMEs).
  2. Groom Deque: The attacker shapes the traffic such that the victim’s unacked_packets_ deque is exactly at its current memory capacity.
  3. Queue Frames: The attacker exhausts the client’s MAX_STREAMS limit to force the client to queue a MAX_STREAMS_FRAME.
  4. Generate Target Packet: The client sends a packet containing both a DATAGRAM_FRAME and the pending MAX_STREAMS_FRAME.
  5. Trigger UAF: The attacker sends an ACK_FRAME for the target packet, bundled with enough stream data/frames to completely fill the client’s packet creator buffer.
  6. Re-entrant Send: During ACK processing, NotifyFramesAcked disables info refreshing due to the DATAGRAM frame. Acknowledging the MAX_STREAMS_FRAME triggers MaybeSendMaxStreamsFrame. Because the packet creator is full (groomed in step 5), AddFrame() triggers a synchronous FlushCurrentPacket().
  7. Relocation: FlushCurrentPacket() serializes the pending frames and calls AddSentPacket(). This pushes a new element to unacked_packets_, exceeding capacity and relocating the deque’s backing array. The info pointer is now dangling.
  8. Heap Grooming: Using concurrent threads or interleaved operations, the attacker reallocates the freed deque backing array with a forged absl::InlinedVector holding a fake QuicFrame.
  9. Arbitrary Delete Primitive: Because QuicFrame uses an unprotected C-style union for its out-of-line pointers (e.g., QuicAckFrame* ack_frame;), the attacker configures their fake frame as an ACK_FRAME and sets the pointer to an arbitrary heap address. MiraclePtr (BRP) does not protect raw union pointers.
  10. RCE: RemoveRetransmittability calls DeleteFrames, executing delete on the attacker’s arbitrary pointer. The attacker uses this primitive to free a TLS context or other sensitive object, achieving a controllable Use-After-Free and ultimate RCE in the Network Service.

Suggested Fix

Do not rely on a persistent pointer/reference to an element inside QuicheCircularDeque across calls that can potentially mutate the deque.

Instead of taking QuicTransmissionInfo*& info, the methods should take the packet_number and dynamically resolve the element index (packet_number - least_unacked_) whenever the QuicTransmissionInfo needs to be accessed after potentially mutating callbacks. Furthermore, the absl::Cleanup logic in NotifyFramesAcked and MaybeAggregateAckedStreamFrame should unconditionally refresh the pointer, or the code should just avoid storing references to deque elements across session_notifier_->OnFrameAcked calls altogether.

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.

View on issue tracker