Low firefox Policy Bypass 🔧 Commit mapped

Overview

Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactlow
DescriptionInformation disclosure in the Networking: HTTP component
ComponentNetworking
Bug ClassPolicy Bypass
Tracker2072436
Fix commit1048a343e4bb (firefox) +34/-77
CISA KEVNot listed
CreditedMozilla
Disclosed2026-10-27

Background

`nsIHttpChannel.redirectTo()` / `apiRedirectToURI`
A channel API that forces an HTTP load to be redirected to a caller-supplied URI. The URI last passed to redirectTo() was readable through the internal attribute nsIHttpChannelInternal.apiRedirectToURI, implemented by HttpBaseChannel::GetApiRedirectToURI (returning mAPIRedirectTo->first()).
e10s channel split
HTTP channels exist as a child half (HttpChannelChild, in the content/renderer process) and a parent half (HttpChannelParent, in the trusted parent process). The real network load is driven by the parent; the child communicates intent over the PHttpChannel IPDL protocol.
`HttpChannelOpenArgs` / `SendRedirect2Verify`
IPDL payloads the child sends to the parent. HttpChannelOpenArgs carries the initial open parameters; RecvRedirect2Verify is the parent handler invoked when the child finishes verifying a redirect. Both previously carried a child-supplied apiRedirectTo / aAPIRedirectURI URI.
`DoAsyncOpen` (`HttpChannelParent.cpp`)
Parent-side routine that builds the real channel from the child’s open args; it previously called httpChannel->RedirectTo(aAPIRedirectToURI) using the URI that arrived from the child.

Root Cause Analysis

The patch removes an entire data path by which a child (content) process could dictate a redirect target that the parent process then honored. Before the fix, HttpChannelChild::ContinueAsyncOpen copied the child’s mAPIRedirectTo->first() into openArgs.apiRedirectTo(), and HttpChannelChild::OnRedirectVerifyCallback queried the prospective redirect channel’s GetApiRedirectToURI and forwarded the resulting redirectURI to the parent via SendRedirect2Verify. On the receiving side, HttpChannelParent::DoAsyncOpen executed if (aAPIRedirectToURI) { httpChannel->RedirectTo(aAPIRedirectToURI); }, and HttpChannelParent::RecvRedirect2Verify executed if (aAPIRedirectURI) { rv = newHttpChannel->RedirectTo(aAPIRedirectURI); }. In Firefox’s threat model the content process is untrusted and can forge any IPDL message it is allowed to send, so this gave a compromised child the ability to force the trusted parent to call RedirectTo() on a parent-side channel with an arbitrary, child-chosen URI — a privilege the content process is not supposed to have (redirect targets are meant to be chosen by the server or by trusted parent-side http-on-modify-request observers, not by content).

The fix deletes the field from the HttpChannelOpenArgs struct (NeckoChannelParams.ipdlh), the apiRedirectTo parameter from the RecvRedirect2Verify message (PHttpChannel.ipdl), the corresponding C++ parameters in DoAsyncOpen/RecvRedirect2Verify and the RedirectTo() calls they drove, the child-side plumbing in ContinueAsyncOpen/OnRedirectVerifyCallback, and the exposed reflection surface GetApiRedirectToURI/apiRedirectToURI entirely. After the patch the API redirect decision can no longer be carried from child to parent, closing the cross-process trust gap. The Mozilla advisory labels this generically as “Information disclosure in Networking: HTTP”; the diff substantiates the more specific defect of the parent trusting a security-sensitive redirect target supplied by the untrusted child, with the disclosure being one possible consequence of that redirect.

Key insight
The parent process honored a redirect-to URI that originated in, and was fully controllable by, the untrusted child process — letting content choose where a parent-driven HTTP load is redirected, which is a trust-boundary violation rather than a normal use of redirectTo().

Attack Path

  1. Compromised renderer An attacker who has already achieved code execution in a content process (a standard Firefox threat-model assumption) controls the PHttpChannel endpoint for its channels.
  2. Forge open/verify args The attacker emits a HttpChannelOpenArgs with a chosen apiRedirectTo, or a Redirect2Verify message with a chosen aAPIRedirectURI, pointing at an attacker-controlled URI (inferred: the exact reachable message depends on channel lifecycle).
  3. Parent honors it HttpChannelParent::DoAsyncOpen / RecvRedirect2Verify call RedirectTo() on the trusted parent-side channel with that URI, with no re-derivation or validation against the child’s actual privileges.
  4. Redirected load The parent-side channel follows the redirect to the attacker’s URI using the channel’s existing load context (inferred), causing the load to go somewhere the content process could not legitimately direct it.
  5. Primitive A content process gains the ability to steer a parent-driven network load to an arbitrary URI; depending on the channel’s origin/credentials this yields cross-origin/information disclosure (the strength of the disclosure is inferred, not proven by the diff).

Impact Assessment

The vulnerable code runs in the parent (trusted networking) process and is driven by messages from the content/renderer process, so the privilege needed is a compromised or malicious content process rather than mere web content. The concrete, diff-substantiated primitive is that such a process can force a parent-side HTTP channel to redirect to an arbitrary URI it chooses. Mozilla rates the issue low severity and frames it as information disclosure; the full strength of any disclosure (which responses become readable, whether credentials ride along) is not demonstrated by the diff and is marked (inferred). There is no memory-safety sink here — this is a cross-process trust/authorization defect, not a UAF/OOB.

Changed Functions

FunctionChangeNotes
GetApiRedirectToURI
netwerk/protocol/http/HttpBaseChannel.cpp
modified
if
netwerk/protocol/http/HttpBaseChannel.cpp
modified
GetResponseTimeoutEnabled
netwerk/protocol/http/HttpBaseChannel.cpp
modified
OnRedirectVerifyCallback
netwerk/protocol/http/HttpChannelChild.cpp
modified
if
netwerk/protocol/http/HttpChannelChild.cpp
modified
if
netwerk/protocol/http/HttpChannelParent.cpp
modified

Files Changed

  • netwerk/ipc/NeckoChannelParams.ipdlh
  • netwerk/protocol/http/HttpBaseChannel.cpp
  • netwerk/protocol/http/HttpBaseChannel.h
  • netwerk/protocol/http/HttpChannelChild.cpp
  • netwerk/protocol/http/HttpChannelParent.cpp
  • netwerk/protocol/http/HttpChannelParent.h
  • netwerk/protocol/http/PHttpChannel.ipdl
  • netwerk/protocol/http/nsIHttpChannelInternal.idl

Audit Directions

  • Child-supplied URIs honored by the parent
    Audit other PHttpChannel/PNecko messages and *OpenArgs fields where a nsIURI sent by the child is used in a parent-side security decision (e.g. RedirectTo, origin/principal derivation, SetTopWindowURI).
  • Other `redirectTo`-style APIs
    Review any remaining code that reflects or forwards mAPIRedirectTo-equivalent state across processes, and other channel reflection attributes exposed to content via nsIHttpChannelInternal.
  • Parent-side `RedirectTo` callers
    Confirm every remaining RedirectTo() call in the parent is driven only by trusted (parent-origin) data, not by anything derived from child IPC.
  • Redirect verify flow
    Re-examine RecvRedirect2Verify and the redirect-chain handshake for other parameters that are trusted from the child without validation.
diff --git a/netwerk/ipc/NeckoChannelParams.ipdlh b/netwerk/ipc/NeckoChannelParams.ipdlh
index 63c4888c075..0018a48c9a6 100644
--- a/netwerk/ipc/NeckoChannelParams.ipdlh
+++ b/netwerk/ipc/NeckoChannelParams.ipdlh
@@ -365,7 +365,6 @@ struct HttpChannelOpenArgs
   nullable nsIURI             original;
   nullable nsIURI             doc;
   nullable nsIReferrerInfo    referrerInfo;
-  nullable nsIURI             apiRedirectTo;
   nullable nsIURI             topWindowURI;
   RequestHeaderTuples         requestHeaders;
   PreferredAlternativeDataTypeParams[] preferredAlternativeTypes;
diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp
index 2f91dd4d2ab..d9a95e45fb2 100644
--- a/netwerk/protocol/http/HttpBaseChannel.cpp
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -4173,16 +4173,6 @@ HttpBaseChannel::SetTlsFlags(uint32_t aTlsFlags) {
   return NS_OK;
 }
 
-NS_IMETHODIMP
-HttpBaseChannel::GetApiRedirectToURI(nsIURI** aResult) {
-  if (!mAPIRedirectTo) {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-  NS_ENSURE_ARG_POINTER(aResult);
-  *aResult = do_AddRef(mAPIRedirectTo->first()).take();
-  return NS_OK;
-}
-
 NS_IMETHODIMP
 HttpBaseChannel::GetResponseTimeoutEnabled(bool* aEnable) {
   if (NS_WARN_IF(!aEnable)) {
diff --git a/netwerk/protocol/http/HttpBaseChannel.h b/netwerk/protocol/http/HttpBaseChannel.h
index 4d3dfea93f4..511179f9f7a 100644
--- a/netwerk/protocol/http/HttpBaseChannel.h
+++ b/netwerk/protocol/http/HttpBaseChannel.h
@@ -292,7 +292,6 @@ class HttpBaseChannel : public nsHashPropertyBag,
   NS_IMETHOD SetIsOCSP(bool value) override;
   NS_IMETHOD GetTlsFlags(uint32_t* aTlsFlags) override;
   NS_IMETHOD SetTlsFlags(uint32_t aTlsFlags) override;
-  NS_IMETHOD GetApiRedirectToURI(nsIURI** aApiRedirectToURI) override;
   [[nodiscard]] virtual nsresult AddSecurityMessage(
       const nsAString& aMessageTag, const nsAString& aMessageCategory);
   NS_IMETHOD TakeAllSecurityMessages(
diff --git a/netwerk/protocol/http/HttpChannelChild.cpp b/netwerk/protocol/http/HttpChannelChild.cpp
index 56a2b56c419..d9d73de1b18 100644
--- a/netwerk/protocol/http/HttpChannelChild.cpp
+++ b/netwerk/protocol/http/HttpChannelChild.cpp
@@ -2049,7 +2049,6 @@ NS_IMETHODIMP
 HttpChannelChild::OnRedirectVerifyCallback(nsresult aResult) {
   LOG(("HttpChannelChild::OnRedirectVerifyCallback [this=%p]\n", this));
   MOZ_ASSERT(NS_IsMainThread());
-  nsCOMPtr<nsIURI> redirectURI;
 
   DebugOnly<nsresult> rv = NS_OK;
 
@@ -2097,13 +2096,6 @@ HttpChannelChild::OnRedirectVerifyCallback(nsresult aResult) {
     // "http-on-modify-request" observers the chance to cancel before that.
     // base->CallOnModifyRequestObservers();
 
-    nsCOMPtr<nsIHttpChannelInternal> newHttpChannelInternal =
-        do_QueryInterface(mRedirectChannelChild);
-    if (newHttpChannelInternal) {
-      (void)newHttpChannelInternal->GetApiRedirectToURI(
-          getter_AddRefs(redirectURI));
-    }
-
     nsCOMPtr<nsIRequest> request = do_QueryInterface(mRedirectChannelChild);
     if (request) {
       request->GetLoadFlags(&loadFlags);
@@ -2125,7 +2117,7 @@ HttpChannelChild::OnRedirectVerifyCallback(nsresult aResult) {
   if (CanSend()) {
     SendRedirect2Verify(aResult, *headerTuples, sourceRequestBlockingReason,
                         targetLoadInfoForwarder, loadFlags, referrerInfo,
-                        redirectURI, corsPreflightArgs);
+                        corsPreflightArgs);
   }
 
   return NS_OK;
@@ -2512,9 +2504,6 @@ nsresult HttpChannelChild::ContinueAsyncOpen() {
   openArgs.uri() = mURI;
   openArgs.original() = mOriginalURI;
   openArgs.doc() = mDocumentURI;
-  if (mAPIRedirectTo) {
-    openArgs.apiRedirectTo() = mAPIRedirectTo->first();
-  }
   openArgs.loadFlags() = mLoadFlags;
   openArgs.requestHeaders() = mClientSetRequestHeaders;
   mRequestHead.Method(openArgs.requestMethod());
diff --git a/netwerk/protocol/http/HttpChannelParent.cpp b/netwerk/protocol/http/HttpChannelParent.cpp
index 88f1dedcaec..783eec56d58 100644
--- a/netwerk/protocol/http/HttpChannelParent.cpp
+++ b/netwerk/protocol/http/HttpChannelParent.cpp
@@ -159,18 +159,17 @@ bool HttpChannelParent::Init(const HttpChannelCreationArgs& aArgs) {
     case HttpChannelCreationArgs::THttpChannelOpenArgs: {
       const HttpChannelOpenArgs& a = aArgs.get_HttpChannelOpenArgs();
       return DoAsyncOpen(
-          a.uri(), a.original(), a.doc(), a.referrerInfo(), a.apiRedirectTo(),
-          a.topWindowURI(), a.loadFlags(), a.requestHeaders(),
-          a.requestMethod(), a.uploadStream(), a.uploadStreamIsStreaming(),
-          a.priority(), a.classOfService(), a.redirectionLimit(),
-          a.thirdPartyFlags(), a.resumeAt(), a.startPos(), a.entityID(),
-          a.allowSpdy(), a.allowHttp3(), a.allowAltSvc(), a.beConservative(),
-          a.bypassProxy(), a.tlsFlags(), a.loadInfo(), a.cacheKey(),
-          a.requestContextID(), a.preflightArgs(), a.initialRwin(),
-          a.blockAuthPrompt(), a.allowStaleCacheContent(),
-          a.preferCacheLoadOverBypass(), a.contentTypeHint(), a.requestMode(),
-          a.redirectMode(), a.channelId(), a.contentWindowId(),
-          a.preferredAlternativeTypes(), a.browserId(),
+          a.uri(), a.original(), a.doc(), a.referrerInfo(), a.topWindowURI(),
+          a.loadFlags(), a.requestHeaders(), a.requestMethod(),
+          a.uploadStream(), a.uploadStreamIsStreaming(), a.priority(),
+          a.classOfService(), a.redirectionLimit(), a.thirdPartyFlags(),
+          a.resumeAt(), a.startPos(), a.entityID(), a.allowSpdy(),
+          a.allowHttp3(), a.allowAltSvc(), a.beConservative(), a.bypassProxy(),
+          a.tlsFlags(), a.loadInfo(), a.cacheKey(), a.requestContextID(),
+          a.preflightArgs(), a.initialRwin(), a.blockAuthPrompt(),
+          a.allowStaleCacheContent(), a.preferCacheLoadOverBypass(),
+          a.contentTypeHint(), a.requestMode(), a.redirectMode(), a.channelId(),
+          a.contentWindowId(), a.preferredAlternativeTypes(), a.browserId(),
           a.launchServiceWorkerStart(), a.launchServiceWorkerEnd(),
           a.dispatchFetchEventStart(), a.dispatchFetchEventEnd(),
           a.handleFetchEventStart(), a.handleFetchEventEnd(),
@@ -421,17 +420,17 @@ void HttpChannelParent::InvokeEarlyHintPreloader(
 
 bool HttpChannelParent::DoAsyncOpen(
     nsIURI* aURI, nsIURI* aOriginalURI, nsIURI* aDocURI,
-    nsIReferrerInfo* aReferrerInfo, nsIURI* aAPIRedirectToURI,
-    nsIURI* aTopWindowURI, const uint32_t& aLoadFlags,
-    const RequestHeaderTuples& requestHeaders, const nsCString& requestMethod,
-    const Maybe<IPCStream>& uploadStream, const bool& uploadStreamIsStreaming,
-    const int16_t& priority, const ClassOfService& classOfService,
-    const uint8_t& redirectionLimit, const uint32_t& thirdPartyFlags,
-    const bool& doResumeAt, const uint64_t& startPos, const nsCString& entityID,
-    const bool& allowSpdy, const bool& allowHttp3, const bool& allowAltSvc,
-    const bool& beConservative, const bool& bypassProxy,
-    const uint32_t& tlsFlags, const LoadInfoArgs& aLoadInfoArgs,
-    const uint32_t& aCacheKey, const uint64_t& aRequestContextID,
+    nsIReferrerInfo* aReferrerInfo, nsIURI* aTopWindowURI,
+    const uint32_t& aLoadFlags, const RequestHeaderTuples& requestHeaders,
+    const nsCString& requestMethod, const Maybe<IPCStream>& uploadStream,
+    const bool& uploadStreamIsStreaming, const int16_t& priority,
+    const ClassOfService& classOfService, const uint8_t& redirectionLimit,
+    const uint32_t& thirdPartyFlags, const bool& doResumeAt,
+    const uint64_t& startPos, const nsCString& entityID, const bool& allowSpdy,
+    const bool& allowHttp3, const bool& allowAltSvc, const bool& beConservative,
+    const bool& bypassProxy, const uint32_t& tlsFlags,
+    const LoadInfoArgs& aLoadInfoArgs, const uint32_t& aCacheKey,
+    const uint64_t& aRequestContextID,
     const Maybe<CorsPreflightArgs>& aCorsPreflightArgs,
     const uint32_t& aInitialRwin, const bool& aBlockAuthPrompt,
     const bool& aAllowStaleCacheContent, const bool& aPreferCacheLoadOverBypass,
@@ -553,10 +552,6 @@ bool HttpChannelParent::DoAsyncOpen(
   httpChannel->SetClassicScriptHintCharset(aClassicScriptHintCharset);
   httpChannel->SetDocumentCharacterSet(aDocumentCharacterSet);
 
-  if (aAPIRedirectToURI) {
-    httpChannel->RedirectTo(aAPIRedirectToURI);
-  }
-
   if (aTopWindowURI) {
     httpChannel->SetTopWindowURI(aTopWindowURI);
   }
@@ -856,7 +851,6 @@ mozilla::ipc::IPCResult HttpChannelParent::RecvRedirect2Verify(
     const uint32_t& aSourceRequestBlockingReason,
     const Maybe<ChildLoadInfoForwarderArgs>& aTargetLoadInfoForwarder,
     const uint32_t& loadFlags, nsIReferrerInfo* aReferrerInfo,
-    nsIURI* aAPIRedirectURI,
     const Maybe<CorsPreflightArgs>& aCorsPreflightArgs) {
   LOG(("HttpChannelParent::RecvRedirect2Verify [this=%p result=%" PRIx32 "]\n",
        this, static_cast<uint32_t>(aResult)));
@@ -873,11 +867,6 @@ mozilla::ipc::IPCResult HttpChannelParent::RecvRedirect2Verify(
         do_QueryInterface(mRedirectChannel);
 
     if (newHttpChannel) {
-      if (aAPIRedirectURI) {
-        rv = newHttpChannel->RedirectTo(aAPIRedirectURI);
-        MOZ_ASSERT(NS_SUCCEEDED(rv));
-      }
-
       for (uint32_t i = 0; i < changedHeaders.Length(); i++) {
         if (changedHeaders[i].mEmpty) {
           rv = newHttpChannel->SetEmptyRequestHeader(changedHeaders[i].mHeader);
diff --git a/netwerk/protocol/http/HttpChannelParent.h b/netwerk/protocol/http/HttpChannelParent.h
index a01de32e8f1..d1e62b01aba 100644
--- a/netwerk/protocol/http/HttpChannelParent.h
+++ b/netwerk/protocol/http/HttpChannelParent.h
@@ -145,18 +145,17 @@ class HttpChannelParent final : public nsIInterfaceRequestor,
 
   [[nodiscard]] bool DoAsyncOpen(
       nsIURI* uri, nsIURI* originalUri, nsIURI* docUri,
-      nsIReferrerInfo* aReferrerInfo, nsIURI* aAPIRedirectToURI,
-      nsIURI* topWindowUri, const uint32_t& loadFlags,
-      const RequestHeaderTuples& requestHeaders, const nsCString& requestMethod,
-      const Maybe<IPCStream>& uploadStream, const bool& uploadStreamIsStreaming,
-      const int16_t& priority, const ClassOfService& classOfService,
-      const uint8_t& redirectionLimit, const uint32_t& thirdPartyFlags,
-      const bool& doResumeAt, const uint64_t& startPos,
-      const nsCString& entityID, const bool& allowSpdy, const bool& allowHttp3,
-      const bool& allowAltSvc, const bool& beConservative,
-      const bool& bypassProxy, const uint32_t& tlsFlags,
-      const LoadInfoArgs& aLoadInfoArgs, const uint32_t& aCacheKey,
-      const uint64_t& aRequestContextID,
+      nsIReferrerInfo* aReferrerInfo, nsIURI* topWindowUri,
+      const uint32_t& loadFlags, const RequestHeaderTuples& requestHeaders,
+      const nsCString& requestMethod, const Maybe<IPCStream>& uploadStream,
+      const bool& uploadStreamIsStreaming, const int16_t& priority,
+      const ClassOfService& classOfService, const uint8_t& redirectionLimit,
+      const uint32_t& thirdPartyFlags, const bool& doResumeAt,
+      const uint64_t& startPos, const nsCString& entityID,
+      const bool& allowSpdy, const bool& allowHttp3, const bool& allowAltSvc,
+      const bool& beConservative, const bool& bypassProxy,
+      const uint32_t& tlsFlags, const LoadInfoArgs& aLoadInfoArgs,
+      const uint32_t& aCacheKey, const uint64_t& aRequestContextID,
       const Maybe<CorsPreflightArgs>& aCorsPreflightArgs,
       const uint32_t& aInitialRwin, const bool& aBlockAuthPrompt,
       const bool& aAllowStaleCacheContent,
@@ -193,7 +192,6 @@ class HttpChannelParent final : public nsIInterfaceRequestor,
       const uint32_t& aSourceRequestBlockingReason,
       const Maybe<ChildLoadInfoForwarderArgs>& aTargetLoadInfoForwarder,
       const uint32_t& loadFlags, nsIReferrerInfo* aReferrerInfo,
-      nsIURI* apiRedirectUri,
       const Maybe<CorsPreflightArgs>& aCorsPreflightArgs) override;
   virtual mozilla::ipc::IPCResult RecvDocumentChannelCleanup(
       const bool& clearCacheEntry) override;
diff --git a/netwerk/protocol/http/PHttpChannel.ipdl b/netwerk/protocol/http/PHttpChannel.ipdl
index e1965ef892d..252eaf812be 100644
--- a/netwerk/protocol/http/PHttpChannel.ipdl
+++ b/netwerk/protocol/http/PHttpChannel.ipdl
@@ -45,7 +45,6 @@ parent:
                         ChildLoadInfoForwarderArgs? targetLoadInfoForwarder,
                         uint32_t loadFlags,
                         nullable nsIReferrerInfo referrerInfo,
-                        nullable nsIURI apiRedirectTo,
                         CorsPreflightArgs? corsPreflightArgs);
 
   // For document loads we keep this protocol open after child's
diff --git a/netwerk/protocol/http/nsIHttpChannelInternal.idl b/netwerk/protocol/http/nsIHttpChannelInternal.idl
index 126748402f5..261c8a69cfb 100644
--- a/netwerk/protocol/http/nsIHttpChannelInternal.idl
+++ b/netwerk/protocol/http/nsIHttpChannelInternal.idl
@@ -239,12 +239,6 @@ interface nsIHttpChannelInternal : nsISupports
      */
     [must_use] attribute unsigned long initialRwin;
 
-    /**
-     * Get value of the URI passed to nsIHttpChannel.redirectTo() if any.
-     * May return null when redirectTo() has not been called.
-     */
-    [must_use] readonly attribute nsIURI apiRedirectToURI;
-
     /**
      * Enable/Disable use of Alternate Services with this channel.
      * The network.http.altsvc.enabled preference is still a pre-requisite.
Loading diff…