Medium CVSS 9.1 firefox Policy Bypass 🔧 Commit mapped

Overview

Medium
Severity
9.1
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionAn attacker was able to bypass the <code>connect-src</code> directive of a Content Security Policy by manipulating subdocuments. This would have also hidden the connections from the Network tab in Devtools.
ComponentNetworking
Bug ClassPolicy Bypass
Tracker1966927
Fix commitdd683d9a9a0c (firefox) +52/-2
CWECWE-693
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
CISA KEVNot listed
CreditedAlan Li (lebr0nli)
Disclosed2025-06-24

Background

`ClientInfo` / `mClientInfo`
Identifies the client (document/worker) that initiated a load. It ties a network request to the initiating context’s CSP so directives like connect-src can be checked against the right policy.
`InternalContentPolicyTypeForFrame`
Factory that builds a LoadInfo for frame/subdocument loads.
`connect-src`
A CSP directive restricting the endpoints a document may connect to (fetch/XHR/WebSocket, etc.). Enforcement must find the originating client’s policy.

Root Cause Analysis

CSP connect-src (and the devtools network attribution) depends on a load being associated with the initiating document’s ClientInfo. The bug is that subdocument manipulation could produce a non-privileged content-process document load whose LoadInfo carried no ClientInfo. With mClientInfo empty, the connection was not matched to the document’s CSP context, so connect-src was not applied and the request also did not appear in the devtools Network panel — effectively an invisible, policy-exempt connection.

The patch adds LoadInfo::IsDocumentMissingClientInfo() and calls it in InternalContentPolicyTypeForFrame: the factory now constructs the LoadInfo into a local RefPtr, and if the new predicate returns true it returns Err(NS_ERROR_CONTENT_BLOCKED) instead of handing back the object (return loadInfo.forget() otherwise). The predicate flags the dangerous case: content process only, mClientInfo is Nothing, there is a loading node (a real document) whose principal is non-system and not a non-content-accessible about: URI, the owner document is not loaded-as-data/resource-doc, and the external content-policy type is not one of the deliberately-exempt types (DTD/OTHER/SPECULATIVE/SAVEAS_DOWNLOAD/DOCUMENT/SUBDOCUMENT). When all those hold, a real sub-resource document load has somehow lost its ClientInfo, so the load is blocked rather than allowed to proceed CSP-unattached. The advisory frames this as a connect-src bypass (CWE-693, protection-mechanism failure); the diff substantiates exactly that — a missing-client-info state let loads escape CSP association — so advisory and code agree.

Key insight
A subdocument-manipulated load could end up with a LoadInfo lacking its initiating ClientInfo, and with no client the connection was never bound to the document’s CSP, so connect-src silently failed to apply (and the request was hidden from devtools); the fix refuses to create such a client-less document LoadInfo.

Attack Path

  1. Setup Attacker page is subject to a restrictive connect-src CSP but wants to exfiltrate to / contact a disallowed endpoint. (web-reachable)
  2. Manipulate subdocuments The attacker arranges a subdocument/frame load so that the resulting LoadInfo is created without a ClientInfo (the precise DOM manipulation is described by the advisory; the diff shows only the resulting client-less state it now rejects — mechanism inferred).
  3. Unattached load With no ClientInfo, the load is not matched to the document’s CSP, so the connect-src check does not apply to it.
  4. Hidden connection Because the request is not tied to the client context, it also does not surface in the devtools Network tab.
  5. Primitive Outbound connections to endpoints forbidden by connect-src, invisible to the page’s own observability — a CSP policy bypass / covert channel.

Impact Assessment

The flaw lives in the content process and is reachable by ordinary web content with no special privilege (CVSS 9.1, no UI). The gained capability is a bypass of the connect-src protection mechanism plus concealment from devtools network logging — an integrity/confidentiality-relevant policy failure, not memory corruption. It is gated to the case where a real, non-privileged document load genuinely lacks a ClientInfo; the specific subdocument sequence that induces that state is not shown by the diff and is (inferred) from the advisory.

Changed Functions

FunctionChangeNotes
if
netwerk/base/LoadInfo.cpp
modified

Files Changed

  • netwerk/base/LoadInfo.cpp
  • netwerk/base/LoadInfo.h

Audit Directions

  • Other `LoadInfo` factories
    CreateForDocument and the other LoadInfo constructors should be checked for the same client-less path, since only InternalContentPolicyTypeForFrame gained the guard.
  • CSP checks keyed on `ClientInfo`
    Audit every enforcement point that resolves CSP via client/reserved-client info for behavior when the client is absent (fail-open vs. fail-closed).
  • Exempt type list
    Review the explicit allowlist of external content types that skip the check (DOCUMENT/SUBDOCUMENT/etc.) to ensure none can smuggle sub-resource semantics.
  • Devtools attribution gaps
    Look for other load paths whose requests can escape the Network panel because they lack client association.
diff --git a/netwerk/base/LoadInfo.cpp b/netwerk/base/LoadInfo.cpp
index 9ebaaf90a79..c9d6cace8de 100644
--- a/netwerk/base/LoadInfo.cpp
+++ b/netwerk/base/LoadInfo.cpp
@@ -84,9 +84,57 @@ static nsContentPolicyType InternalContentPolicyTypeForFrame(
     const Maybe<mozilla::dom::ClientInfo>& aLoadingClientInfo,
     const Maybe<mozilla::dom::ServiceWorkerDescriptor>& aController,
     uint32_t aSandboxFlags) {
-  return MakeAndAddRef<LoadInfo>(
+  RefPtr<LoadInfo> loadInfo(new LoadInfo(
       aLoadingPrincipal, aTriggeringPrincipal, aLoadingContext, aSecurityFlags,
-      aContentPolicyType, aLoadingClientInfo, aController, aSandboxFlags);
+      aContentPolicyType, aLoadingClientInfo, aController, aSandboxFlags));
+  if (loadInfo->IsDocumentMissingClientInfo()) {
+    return Err(NS_ERROR_CONTENT_BLOCKED);
+  }
+  return loadInfo.forget();
+}
+
+bool LoadInfo::IsDocumentMissingClientInfo() {
+  // Only check in the content process for now.
+  if (!XRE_IsContentProcess() || mClientInfo.isSome()) {
+    return false;
+  }
+
+  // No node means no document, so there is nothing to check.
+  nsCOMPtr<nsINode> node = LoadingNode();
+  if (!node) {
+    return false;
+  }
+
+  // Don't bother checking loads that will end up in a privileged context (for
+  // now).
+  if (mLoadingPrincipal->IsSystemPrincipal()) {
+    return false;
+  }
+  if (mLoadingPrincipal->SchemeIs("about") &&
+      !mLoadingPrincipal->IsContentAccessibleAboutURI()) {
+    return false;
+  }
+
+  // The nsDataDocumentContentPolicy is responsible restricting these documents.
+  Document* doc = node->OwnerDoc();
+  if (doc->IsLoadedAsData() || doc->IsResourceDoc()) {
+    return false;
+  }
+
+  ExtContentPolicy externalType = nsILoadInfo::GetExternalContentPolicyType();
+  if (externalType == ExtContentPolicy::TYPE_DTD ||
+      externalType == ExtContentPolicy::TYPE_OTHER ||
+      externalType == ExtContentPolicy::TYPE_SPECULATIVE ||
+      externalType == ExtContentPolicy::TYPE_SAVEAS_DOWNLOAD ||
+      externalType == ExtContentPolicy::TYPE_DOCUMENT ||
+      externalType == ExtContentPolicy::TYPE_SUBDOCUMENT) {
+    return false;
+  }
+
+  NS_WARNING(
+      "Prevented the creation of a LoadInfo for a document without a "
+      "ClientInfo!");
+  return true;
 }
 
 /* static */ already_AddRefed<LoadInfo> LoadInfo::CreateForDocument(
diff --git a/netwerk/base/LoadInfo.h b/netwerk/base/LoadInfo.h
index 76701d49523..7946dacf862 100644
--- a/netwerk/base/LoadInfo.h
+++ b/netwerk/base/LoadInfo.h
@@ -295,6 +295,8 @@ class LoadInfo final : public nsILoadInfo {
   void ComputeIsThirdPartyContext(nsPIDOMWindowOuter* aOuterWindow);
   void ComputeIsThirdPartyContext(dom::WindowGlobalParent* aGlobal);
 
+  bool IsDocumentMissingClientInfo();
+
   // This function is the *only* function which can change the securityflags
   // of a loadinfo. It only exists because of the XHR code. Don't call it
   // from anywhere else!
Loading diff…