197052359826ba5d3b11842aeaf461235a2f9424 [Code health] Remove dangling pointer in QuicChromiumClientSession
The bug at a glance
The code bound and later used a raw QuicPacketWriter* via UnsafeDanglingUntriaged that could be replaced and destroyed during a prior migration, leaving MigrateSessionOnWriteError operating on a dangling pointer; it is a genuine use-after-free class, but it sits on the write-error/network-migration path and the pointer was used only for an identity comparison, limiting controllability.
A network-positioned attacker (or adverse network conditions) that induces write errors and triggers rapid connection/socket migrations could race the writer’s replacement against the deferred MigrateSessionOnWriteError task, turning a QUIC migration edge case into a dangling-pointer use.
Root cause
On a write error, the session posted MigrateSessionOnWriteError bound to connection()->writer() wrapped in base::UnsafeDanglingUntriaged. If a migration (MigratePath) replaced and destroyed that writer before the posted task ran, the task held a dangling QuicPacketWriter*; comparing writer != connection()->writer() and any callback use then touched freed memory. The patch replaces the raw-pointer capture with a monotonic packet_writer_generation_ counter (incremented after each MigratePath) and compares generations instead, so a stale task simply aborts without ever referencing the freed writer.
Key code
Dangling writer pointer replaced by a generation counter
- base::UnsafeDanglingUntriaged(connection()->writer())));
+ packet_writer_generation_));
...
- if (writer != connection()->writer() ||
+ if (writer_generation != packet_writer_generation_ ||
Patch walkthrough
net/quic/quic_chromium_client_session.cc— Stops binding the raw connection writer pointer (UnsafeDanglingUntriaged) into the posted migration task and instead passes packet_writer_generation_, comparing generations to detect a replaced writer; increments the generation after MigratePath so a stale task aborts without dereferencing freed memory.net/quic/quic_chromium_client_session.h— Changes MigrateSessionOnWriteError’s signature from QuicPacketWriter* to uint64_t writer_generation and adds the packet_writer_generation_ member, removing the dangling-pointer parameter from the API surface.
Background
QuicChromiumClientSession — Chrome’s net-stack object managing a QUIC connection, including connection/socket migration on network changes and write errors.
UnsafeDanglingUntriaged — A base annotation that opts a raw pointer out of MiraclePtr dangling-pointer protection, used as a temporary suppression for known-risky pointers.
Connection migration — QUIC’s ability to move a session to a new network path/socket, which can replace and destroy the previous QuicPacketWriter.
Vulnerability window
- Reachability — A live QUIC session with connection-migration-v2 enabled encounters a packet write error (network-path dependent, e.g. interface change or adverse network).
- Trigger — HandleWriteError posts MigrateSessionOnWriteError bound to the current writer, and a concurrent MigratePath replaces/destroys that writer before the task runs (inferred race window).
- Primitive — The deferred task holds a dangling QuicPacketWriter*, yielding a use-after-free read/compare and potential callback use on freed memory (inferred).
- Escalation — Converting the dangling read into a controlled reuse/corruption would require heap grooming of the freed writer allocation, which the diff neither shows nor enables (inferred).
Proof of concept
Conceptually, establish a QUIC session with connection migration enabled, induce a socket write error, and race a network-change-driven MigratePath so the original writer is freed before the posted MigrateSessionOnWriteError task executes; the pre-patch build would then operate on the freed writer. No exploit code provided.
Exploitation
- Setup — Attacker influences network conditions to force QUIC write errors and migrations against a victim session (inferred).
- Race — Timing is arranged so writer replacement/destruction completes before the deferred migration task runs (inferred).
- Use — Deferred task touches the freed writer; turning this into memory corruption requires reclaiming the freed allocation with controlled data (inferred).
Detection & hunting
For defenders and detection engineers:
- grep for base::UnsafeDanglingUntriaged and UnsafeDangling across net/ and anywhere a raw pointer is bound into a PostTask/BindOnce — Flag deferred tasks that capture a raw pointer to an object whose lifetime can end before the task runs; prefer weak pointers, generation counters, or ownership transfer.
- ASAN/MiraclePtr crash reports in QuicChromiumClientSession::MigrateSessionOnWriteError — Hunt use-after-free reports on the write-error migration path in net QUIC fuzzing and canary crash telemetry.
Audit directions
- Remaining UnsafeDanglingUntriaged/UnsafeDangling call sites in net/ and QUIC session migration code — Audit each suppression for a replace-then-use lifetime hazard and migrate it to a generation counter or weak reference as done here.
- PostTask callbacks capturing connection()/writer()/socket raw pointers — Look for other migration or error-handling tasks that assume the captured transport object survives until the task executes.