CVE-2026-118000
Overview
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 attributensIHttpChannelInternal.apiRedirectToURI, implemented byHttpBaseChannel::GetApiRedirectToURI(returningmAPIRedirectTo->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 thePHttpChannelIPDL protocol. - `HttpChannelOpenArgs` / `SendRedirect2Verify`
- IPDL payloads the child sends to the parent.
HttpChannelOpenArgscarries the initial open parameters;RecvRedirect2Verifyis the parent handler invoked when the child finishes verifying a redirect. Both previously carried a child-suppliedapiRedirectTo/aAPIRedirectURIURI. - `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.
redirectTo().Attack Path
- Compromised renderer
An attacker who has already achieved code execution in a content process (a standard Firefox threat-model assumption) controls the
PHttpChannelendpoint for its channels. - Forge open/verify args
The attacker emits a
HttpChannelOpenArgswith a chosenapiRedirectTo, or aRedirect2Verifymessage with a chosenaAPIRedirectURI, pointing at an attacker-controlled URI (inferred: the exact reachable message depends on channel lifecycle). - Parent honors it
HttpChannelParent::DoAsyncOpen/RecvRedirect2VerifycallRedirectTo()on the trusted parent-side channel with that URI, with no re-derivation or validation against the child’s actual privileges. - 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.
- 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
Changed Functions
| Function | Change | Notes |
|---|---|---|
GetApiRedirectToURInetwerk/protocol/http/HttpBaseChannel.cpp |
modified | |
ifnetwerk/protocol/http/HttpBaseChannel.cpp |
modified | |
GetResponseTimeoutEnablednetwerk/protocol/http/HttpBaseChannel.cpp |
modified | |
OnRedirectVerifyCallbacknetwerk/protocol/http/HttpChannelChild.cpp |
modified | |
ifnetwerk/protocol/http/HttpChannelChild.cpp |
modified | |
ifnetwerk/protocol/http/HttpChannelParent.cpp |
modified |
Files Changed
netwerk/ipc/NeckoChannelParams.ipdlhnetwerk/protocol/http/HttpBaseChannel.cppnetwerk/protocol/http/HttpBaseChannel.hnetwerk/protocol/http/HttpChannelChild.cppnetwerk/protocol/http/HttpChannelParent.cppnetwerk/protocol/http/HttpChannelParent.hnetwerk/protocol/http/PHttpChannel.ipdlnetwerk/protocol/http/nsIHttpChannelInternal.idl
Audit Directions
- Child-supplied URIs honored by the parentAudit other
PHttpChannel/PNeckomessages and*OpenArgsfields where ansIURIsent by the child is used in a parent-side security decision (e.g.RedirectTo, origin/principal derivation,SetTopWindowURI). - Other `redirectTo`-style APIsReview any remaining code that reflects or forwards
mAPIRedirectTo-equivalent state across processes, and other channel reflection attributes exposed to content viansIHttpChannelInternal. - Parent-side `RedirectTo` callersConfirm every remaining
RedirectTo()call in the parent is driven only by trusted (parent-origin) data, not by anything derived from child IPC. - Redirect verify flowRe-examine
RecvRedirect2Verifyand the redirect-chain handshake for other parameters that are trusted from the child without validation.
Patch
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.