CVE-2025-6427
Overview
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-srccan be checked against the right policy. - `InternalContentPolicyTypeForFrame`
- Factory that builds a
LoadInfofor 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.
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
- Setup
Attacker page is subject to a restrictive
connect-srcCSP but wants to exfiltrate to / contact a disallowed endpoint. (web-reachable) - Manipulate subdocuments
The attacker arranges a subdocument/frame load so that the resulting
LoadInfois created without aClientInfo(the precise DOM manipulation is described by the advisory; the diff shows only the resulting client-less state it now rejects — mechanism inferred). - Unattached load
With no
ClientInfo, the load is not matched to the document’s CSP, so theconnect-srccheck does not apply to it. - Hidden connection Because the request is not tied to the client context, it also does not surface in the devtools Network tab.
- Primitive
Outbound connections to endpoints forbidden by
connect-src, invisible to the page’s own observability — a CSP policy bypass / covert channel.
Impact Assessment
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
| Function | Change | Notes |
|---|---|---|
ifnetwerk/base/LoadInfo.cpp |
modified |
Files Changed
netwerk/base/LoadInfo.cppnetwerk/base/LoadInfo.h
Audit Directions
- Other `LoadInfo` factories
CreateForDocumentand the otherLoadInfoconstructors should be checked for the same client-less path, since onlyInternalContentPolicyTypeForFramegained 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 listReview the explicit allowlist of external content types that skip the check (DOCUMENT/SUBDOCUMENT/etc.) to ensure none can smuggle sub-resource semantics.
- Devtools attribution gapsLook for other load paths whose requests can escape the Network panel because they lack client association.
Patch
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!