Medium firefox Logic Error 🔧 Commit mapped

Overview

Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionMitigation bypass in the DOM: Networking component
ComponentDOM
Bug ClassLogic Error
Tracker2041902
Fix commit20d7ad5ebb76 (firefox) +19/-20
CISA KEVNot listed
CreditedIbuki Sato and Tomoya Nakanishi
Disclosed2026-07-21

Background

WebTransport
A DOM networking API that opens an HTTP/3-based bidirectional transport to a server from a content process.
`ClientInfo` / `IPCClientInfo`
A structure identifying the originating client (window or worker) whose IPC form (IPCClientInfo) is sent from the content process to the parent to associate the connection with a security context.
`mozilla::Maybe<T>`
Gecko’s optional wrapper that either holds a value (Some) or explicitly holds nothing (Nothing), distinguishing genuine absence from a fabricated default.
`WebTransportParent`
The parent-process (PBackground) actor that actually performs the privileged AsyncConnectWithClient on behalf of a content-process request.

Root Cause Analysis

On the content side, WebTransport::Init refused to proceed when mGlobal->GetClientInfo() was Nothing, throwing NS_ERROR_DOM_INVALID_STATE_ERR, so the honest path only ever transmitted a populated IPCClientInfo. However, the IPDL message CreateWebTransportParent declared aClientInfo as a mandatory, non-nullable IPCClientInfo, meaning the parent could never observe “no client” and instead received a value that a compromised or malicious content process could freely fabricate. In WebTransportParent::Create, the parent unconditionally wrapped this attacker-influenced field as Some(ClientInfo{aClientInfo}) and fed it into AsyncConnectWithClient, so any client-info-based security or partitioning decision downstream operated on unvalidated, spoofable data — the violated invariant being that a client identity presented to the parent must reflect a real, verified client.

The fix changes the IPDL field to nullable (IPCClientInfo?) and threads mozilla::Maybe<IPCClientInfo> end-to-end, so WebTransport::Init now sends Nothing when there is no client rather than throwing, and the parent forwards the honest Maybe (including Nothing) into AsyncConnectWithClient instead of manufacturing a Some. This works because the parent no longer coerces a possibly-bogus value into a valid-looking client identity, letting the connection code correctly handle genuine absence.

Key insight
The single core mistake was modeling an optional client identity as a mandatory IPC field and then always wrapping it in Some, which let the parent treat a spoofable or absent value as a legitimate present one; the fix restores the optionality (Maybe<IPCClientInfo> / IPCClientInfo?) so absence is represented truthfully across the trust boundary.

Attack Path

  1. Compromise or drive a content process An attacker running script in (or having compromised) a content process controls the arguments marshalled into the CreateWebTransportParent IPC message.
  2. Supply a forged mandatory client info Because aClientInfo was non-nullable, the sender can populate it with an arbitrary or default IPCClientInfo rather than a genuine, verified client identity.
  3. Parent accepts and wraps the value BackgroundParentImpl::RecvCreateWebTransportParent forwards the field into WebTransportParent::Create, which unconditionally builds Some(ClientInfo{aClientInfo}).
  4. Privileged connect uses the spoofed identity The parent passes the fabricated client info into AsyncConnectWithClient, so any mitigation keyed on client identity is evaluated against attacker-controlled data.

Impact Assessment

An attacker with content-process control gains a mitigation bypass in the parent process’s DOM networking path by presenting a fabricated or absent client identity as though it were a valid one, causing the privileged WebTransport connection to be established under an identity the client did not legitimately hold. This occurs in the parent (PBackground) process during WebTransport setup; the precondition is the ability to send the CreateWebTransportParent IPC message (script in a content process, or a compromised content process). Consistent with the metadata, this is a moderate/medium logic error rather than direct memory corruption.

Changed Functions

FunctionChangeNotes
if
dom/webtransport/api/WebTransport.cpp
modified

Files Changed

  • dom/webtransport/api/WebTransport.cpp
  • dom/webtransport/parent/WebTransportParent.cpp
  • dom/webtransport/parent/WebTransportParent.h
  • ipc/glue/BackgroundParentImpl.cpp
  • ipc/glue/BackgroundParentImpl.h
  • ipc/glue/PBackground.ipdl

Audit Directions

  • Non-nullable IPC fields that model optional data
    Review IPDL messages where a logically optional identity or context (client info, principals, tokens) is declared mandatory, since senders can then supply defaults the receiver cannot distinguish from real values.
  • Parent-side wrapping of untrusted values in `Some`
    Flag parent-process code that unconditionally converts an IPC-received value into Some(...) or a “valid” wrapper, which hides absence and forces downstream security checks onto unvalidated data.
  • Client-info / identity consumers in privileged connect paths
    Trace all callers of AsyncConnectWithClient and similar routines to confirm they validate and correctly handle Nothing rather than trusting a caller-provided identity.
diff --git a/dom/webtransport/api/WebTransport.cpp b/dom/webtransport/api/WebTransport.cpp
index 332dba455d1..23525bc5884 100644
--- a/dom/webtransport/api/WebTransport.cpp
+++ b/dom/webtransport/api/WebTransport.cpp
@@ -287,13 +287,12 @@ void WebTransport::Init(const GlobalObject& aGlobal, const nsAString& aURL,
     return;
   }
 
-  if (mGlobal->GetClientInfo().isNothing()) {
-    aError.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-    return;
-  }
-  IPCClientInfo ipcClientInfo = mGlobal->GetClientInfo().ref().ToIPC();
-
   nsCOMPtr<nsIPrincipal> principal = mGlobal->PrincipalOrNull();
+  mozilla::Maybe<IPCClientInfo> ipcClientInfo;
+
+  if (mGlobal->GetClientInfo().isSome()) {
+    ipcClientInfo = mozilla::Some(mGlobal->GetClientInfo().ref().ToIPC());
+  }
 
   nsPIDOMWindowInner* window = mGlobal->GetAsInnerWindow();
   if (window) {
diff --git a/dom/webtransport/parent/WebTransportParent.cpp b/dom/webtransport/parent/WebTransportParent.cpp
index ff25539eb8f..2a56118b34d 100644
--- a/dom/webtransport/parent/WebTransportParent.cpp
+++ b/dom/webtransport/parent/WebTransportParent.cpp
@@ -33,9 +33,9 @@ WebTransportParent::~WebTransportParent() {
 
 void WebTransportParent::Create(
     const nsAString& aURL, nsIPrincipal* aPrincipal,
-    const uint64_t& aBrowsingContextID, const IPCClientInfo& aClientInfo,
-    const bool& aDedicated, const bool& aRequireUnreliable,
-    const uint32_t& aCongestionControl,
+    const uint64_t& aBrowsingContextID,
+    const mozilla::Maybe<IPCClientInfo>& aClientInfo, const bool& aDedicated,
+    const bool& aRequireUnreliable, const uint32_t& aCongestionControl,
     nsTArray<WebTransportHash>&& aServerCertHashes,
     Endpoint<PWebTransportParent>&& aParentEndpoint,
     std::function<void(std::tuple<const nsresult&, const uint8_t&>)>&&
@@ -93,11 +93,11 @@ void WebTransportParent::Create(
        nsServerCertHashes = std::move(nsServerCertHashes),
        principal = RefPtr{aPrincipal}, browsingContextID = aBrowsingContextID,
        flags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
-       clientInfo = ClientInfo{aClientInfo}] {
+       clientInfo = aClientInfo] {
         LOG(("WebTransport %p AsyncConnect", self.get()));
         if (NS_FAILED(self->mWebTransport->AsyncConnectWithClient(
                 uri, dedicated, std::move(nsServerCertHashes), principal,
-                browsingContextID, flags, self, Some(clientInfo),
+                browsingContextID, flags, self, clientInfo,
                 nsIWebTransport::HTTPVersion::h3))) {
           LOG(("AsyncConnect failure; we should get OnSessionClosed"));
         }
diff --git a/dom/webtransport/parent/WebTransportParent.h b/dom/webtransport/parent/WebTransportParent.h
index 766026317dd..1215e99fbf5 100644
--- a/dom/webtransport/parent/WebTransportParent.h
+++ b/dom/webtransport/parent/WebTransportParent.h
@@ -33,8 +33,8 @@ class WebTransportParent : public PWebTransportParent,
 
   void Create(const nsAString& aURL, nsIPrincipal* aPrincipal,
               const uint64_t& aBrowsingContextID,
-              const IPCClientInfo& aClientInfo, const bool& aDedicated,
-              const bool& aRequireUnreliable,
+              const mozilla::Maybe<IPCClientInfo>& aClientInfo,
+              const bool& aDedicated, const bool& aRequireUnreliable,
               const uint32_t& aCongestionControl,
               nsTArray<WebTransportHash>&& aServerCertHashes,
               Endpoint<PWebTransportParent>&& aParentEndpoint,
diff --git a/ipc/glue/BackgroundParentImpl.cpp b/ipc/glue/BackgroundParentImpl.cpp
index 65e6c92d41f..95dd1148a1a 100644
--- a/ipc/glue/BackgroundParentImpl.cpp
+++ b/ipc/glue/BackgroundParentImpl.cpp
@@ -463,9 +463,9 @@ mozilla::ipc::IPCResult BackgroundParentImpl::RecvCreateFileSystemManagerParent(
 
 mozilla::ipc::IPCResult BackgroundParentImpl::RecvCreateWebTransportParent(
     const nsAString& aURL, nsIPrincipal* aPrincipal,
-    const uint64_t& aBrowsingContextID, const IPCClientInfo& aClientInfo,
-    const bool& aDedicated, const bool& aRequireUnreliable,
-    const uint32_t& aCongestionControl,
+    const uint64_t& aBrowsingContextID,
+    const mozilla::Maybe<IPCClientInfo>& aClientInfo, const bool& aDedicated,
+    const bool& aRequireUnreliable, const uint32_t& aCongestionControl,
     nsTArray<WebTransportHash>&& aServerCertHashes,
     Endpoint<PWebTransportParent>&& aParentEndpoint,
     CreateWebTransportParentResolver&& aResolver) {
diff --git a/ipc/glue/BackgroundParentImpl.h b/ipc/glue/BackgroundParentImpl.h
index 8a6f056be76..429fd9b3f57 100644
--- a/ipc/glue/BackgroundParentImpl.h
+++ b/ipc/glue/BackgroundParentImpl.h
@@ -121,9 +121,9 @@ class BackgroundParentImpl : public PBackgroundParent {
 
   mozilla::ipc::IPCResult RecvCreateWebTransportParent(
       const nsAString& aURL, nsIPrincipal* aPrincipal,
-      const uint64_t& aBrowsingContextID, const IPCClientInfo& aClientInfo,
-      const bool& aDedicated, const bool& aRequireUnreliable,
-      const uint32_t& aCongestionControl,
+      const uint64_t& aBrowsingContextID,
+      const mozilla::Maybe<IPCClientInfo>& aClientInfo, const bool& aDedicated,
+      const bool& aRequireUnreliable, const uint32_t& aCongestionControl,
       nsTArray<WebTransportHash>&& aServerCertHashes,
       Endpoint<PWebTransportParent>&& aParentEndpoint,
       CreateWebTransportParentResolver&& aResolver) override;
diff --git a/ipc/glue/PBackground.ipdl b/ipc/glue/PBackground.ipdl
index bfc523c53e0..3575bc03208 100644
--- a/ipc/glue/PBackground.ipdl
+++ b/ipc/glue/PBackground.ipdl
@@ -178,7 +178,7 @@ parent:
       nsString aURL,
       nullable nsIPrincipal aPrincipal,
       uint64_t aBrowsingContextID,
-      IPCClientInfo aClientInfo,
+      IPCClientInfo?  aClientInfo,
       bool aDedicated,
       bool aRequireUnreliable,
       uint32_t aCongestionControl,
Loading diff…