High CVSS 7.5 firefox UAF 🔧 Commit mapped

Overview

High
Severity
7.5
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionIt was possible to cause a use-after-free in the content process side of a WebTransport connection, leading to a potentially exploitable crash.
ComponentDOM
Bug ClassUAF
Tracker1944126
Fix commitb778e5e9455b (firefox) +10/-5
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
CISA KEVNot listed
Creditedsherkito
Disclosed2025-03-04

Background

WebTransport
A bidirectional client/server transport API exposed to web content. In Firefox its WebTransport DOM object lives in the content process and is driven over IPC by a socket-process/parent actor.
`WebTransportChild`
The content-process IPDL child actor that receives Recv* messages (remote closed, new streams, datagrams, stream reset) and forwards them to the DOM WebTransport object via its mTransport member.
RefPtr / strong reference
Gecko’s intrusively ref-counted smart pointer. Holding a local RefPtr keeps an object alive for the duration of a call even if other references are dropped mid-call.
Re-entrant destruction
A callback that, as a side effect, can cause the object it was dispatched to (or the member holding it) to be released, so a bare member pointer can dangle before the call returns.

Root Cause Analysis

Each Recv* handler in dom/webtransport/child/WebTransportChild.cpp checked if (mTransport) and then immediately called a method on the raw member (mTransport->RemoteClosed(...), mTransport->NewBidirectionalStream(...), mTransport->NewUnidirectionalStream(...), mTransport->NewDatagramReceived(...), mTransport->OnStreamResetOrStopSending(...)). The invariant being violated is that mTransport must remain alive for the full duration of the forwarded call. These WebTransport methods can, as a side effect, tear down the connection — dispatching events to script, resolving/rejecting promises, or otherwise driving state that releases the last strong reference to the WebTransport object and clears/drops mTransport. When that happens re-entrantly, the in-flight mTransport->... call continues operating on freed memory: a use-after-free in the content process.

The patch takes a local strong reference before each dispatch — RefPtr<WebTransport> self(mTransport); self->RemoteClosed(...) (and the analogous change in the other four handlers). The local RefPtr guarantees the WebTransport instance stays alive until the handler returns, even if the message processing drops every other reference to it. This is the canonical Gecko lifetime fix: convert a transient raw-pointer dereference of a ref-counted member into a kept-alive RefPtr over the call.

Key insight
The Recv handlers dereferenced the ref-counted mTransport member across a call that can re-entrantly destroy the WebTransport object, so a well-timed teardown freed the object mid-call; keeping a local RefPtr<WebTransport> alive for the duration removes the dangling dereference.

Attack Path

  1. Attacker server Lures content into opening a WebTransport session to an attacker-controlled endpoint (web-reachable; WebTransport is script-exposed).
  2. Trigger teardown-in-callback The server sends a sequence (e.g. remote-close, incoming stream, datagram, or stream reset) that causes the DOM WebTransport method to run script/promise logic which drops the last reference and clears mTransport during the call (inferred: exact closing sequence not shown by the diff).
  3. Dangling use The still-executing mTransport->... call continues on the just-freed WebTransport object.
  4. Primitive Use-after-free in the content process: a potentially exploitable crash, and with heap grooming a controllable freed-object reuse (memory-corruption primitive; exploitability beyond crash is inferred).

Impact Assessment

This is a content-process (renderer) use-after-free, reachable with no special privilege by any page that can open a WebTransport session to an attacker’s server and with no user interaction (the CVSS vector is network, AC:L, PR:N, UI:N, A:H). The directly substantiated primitive is a UAF of a WebTransport object leading to a potentially exploitable crash; escalation to code execution would require shaping the freed allocation and is not demonstrated by the diff (inferred). Confidentiality/integrity are rated none in the vector, consistent with a crash-grade availability impact that may be upgradable with additional work.

Changed Functions

FunctionChangeNotes
if
dom/webtransport/child/WebTransportChild.cpp
modified

Files Changed

  • dom/webtransport/child/WebTransportChild.cpp

Audit Directions

  • Other IPDL actors forwarding to ref-counted members
    Audit Recv* handlers that do if (mFoo) mFoo->Method(...) on a ref-counted DOM object; any that can re-entrantly release mFoo need the same local-RefPtr guard.
  • WebTransport teardown paths
    Trace which WebTransport methods can synchronously run script or resolve promises, and verify all of their callers hold the object alive.
  • `ActorDestroy` / `CloseAll` interplay
    Check whether actor destruction can null mTransport concurrently with message dispatch and that the new RefPtr fully covers those windows.
diff --git a/dom/webtransport/child/WebTransportChild.cpp b/dom/webtransport/child/WebTransportChild.cpp
index 12714b003e9..e7c279398cf 100644
--- a/dom/webtransport/child/WebTransportChild.cpp
+++ b/dom/webtransport/child/WebTransportChild.cpp
@@ -34,7 +34,8 @@ void WebTransportChild::CloseAll() {
 ::mozilla::ipc::IPCResult WebTransportChild::RecvRemoteClosed(
     const bool& aCleanly, const uint32_t& aCode, const nsACString& aReason) {
   if (mTransport) {
-    mTransport->RemoteClosed(aCleanly, aCode, aReason);
+    RefPtr<WebTransport> self(mTransport);
+    self->RemoteClosed(aCleanly, aCode, aReason);
   }
   return IPC_OK();
 }
@@ -43,7 +44,8 @@ void WebTransportChild::CloseAll() {
     const uint64_t& aStreamId, const RefPtr<DataPipeReceiver>& aIncoming,
     const RefPtr<DataPipeSender>& aOutgoing) {
   if (mTransport) {
-    mTransport->NewBidirectionalStream(aStreamId, aIncoming, aOutgoing);
+    RefPtr<WebTransport> self(mTransport);
+    self->NewBidirectionalStream(aStreamId, aIncoming, aOutgoing);
   }
   return IPC_OK();
 }
@@ -51,7 +53,8 @@ void WebTransportChild::CloseAll() {
 ::mozilla::ipc::IPCResult WebTransportChild::RecvIncomingUnidirectionalStream(
     const uint64_t& aStreamId, const RefPtr<DataPipeReceiver>& aStream) {
   if (mTransport) {
-    mTransport->NewUnidirectionalStream(aStreamId, aStream);
+    RefPtr<WebTransport> self(mTransport);
+    self->NewUnidirectionalStream(aStreamId, aStream);
   }
   return IPC_OK();
 }
@@ -59,7 +62,8 @@ void WebTransportChild::CloseAll() {
 ::mozilla::ipc::IPCResult WebTransportChild::RecvIncomingDatagram(
     nsTArray<uint8_t>&& aData, const TimeStamp& aRecvTimeStamp) {
   if (mTransport) {
-    mTransport->NewDatagramReceived(std::move(aData), aRecvTimeStamp);
+    RefPtr<WebTransport> self(mTransport);
+    self->NewDatagramReceived(std::move(aData), aRecvTimeStamp);
   }
   return IPC_OK();
 }
@@ -67,7 +71,8 @@ void WebTransportChild::CloseAll() {
 ::mozilla::ipc::IPCResult WebTransportChild::RecvOnStreamResetOrStopSending(
     const uint64_t& aStreamId, const StreamResetOrStopSendingError& aError) {
   if (mTransport) {
-    mTransport->OnStreamResetOrStopSending(aStreamId, aError);
+    RefPtr<WebTransport> self(mTransport);
+    self->OnStreamResetOrStopSending(aStreamId, aError);
   }
   return IPC_OK();
 }
Loading diff…