High firefox Logic Error 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionPrivilege escalation in the DOM: Navigation component
ComponentNetworking
Bug ClassLogic Error
Tracker2054416
Fix commit3ec5c71b41bb (firefox) +13/-5
CISA KEVNot listed
Creditedchoeseyeong
Disclosed2026-08-18

Background

`nsDocShellLoadState`
The serializable descriptor of a pending navigation (URI, triggering principal, flags) passed over IPC between content and parent processes.
`mRemoteTypeOverride`
An optional field on the load state that forces a document to load into a specific content-process type, a decision that is meant to be made only by the trusted parent process.
`mTriggeringRemoteType`
Identifies which process initiated the load; the sentinel NOT_REMOTE_TYPE marks a load originating in the parent rather than a content process.
`DocumentLoadListener::Open`
The parent-process entry point that begins a channel-based document load and enforces preconditions before honoring a remote-type override.

Root Cause Analysis

The IPC-deserialization constructor of nsDocShellLoadState validated cross-process javascript: loads but never checked whether an untrusted content process had populated mRemoteTypeOverride, an override that is supposed to be settable only by the parent. Because that field was accepted verbatim from the sending process, a compromised content process could hand the parent a load state that dictated the destination process type, violating the invariant that process-placement decisions are exclusively a parent privilege.

The fix restructures the guard so that whenever mTriggeringRemoteType != NOT_REMOTE_TYPE (the load came from a content process) and mRemoteTypeOverride.isSome(), the parent raises a FatalError and rejects the actor. DocumentLoadListener::Open is additionally hardened to refuse an override when the load has an effective triggering remote type or originates from session history, closing paths that could reintroduce the same trust confusion. Together the changes re-establish that only parent-originated loads may carry a remote-type override.

Key insight
The single mistake was trusting a content-process-supplied mRemoteTypeOverride field without verifying that the load originated in the parent; the fix rejects any content-triggered load state that carries an override, restoring the parent-only invariant on process placement.

Attack Path

  1. Compromise a content process The attacker first gains code execution in a sandboxed content process, for example through a separate renderer bug.
  2. Forge a load state The compromised process constructs an nsDocShellLoadState for an about:blank top-level load and sets mRemoteTypeOverride to a target process type of its choosing.
  3. Send it to the parent The forged load state is delivered over IPC, where the pre-patch constructor accepts the override because it only screened javascript: schemes.
  4. Coerce process placement DocumentLoadListener::Open honors the attacker-chosen override, directing the document into a more privileged process than the content process should be able to reach.
  5. Escalate privileges Executing content in that higher-privileged process context yields capabilities beyond the original sandbox.

Impact Assessment

An attacker who already controls a sandboxed content process gains the ability to force a document into a process type of their choosing, escalating privilege within the browser’s multi-process architecture. The escalation is realized in the parent-mediated process-selection path and the resulting privileged content process. The precondition is prior code execution in a content process capable of sending a crafted load state over IPC.

Changed Functions

FunctionChangeNotes
if
docshell/base/nsDocShellLoadState.cpp
modified

Files Changed

  • docshell/base/nsDocShellLoadState.cpp
  • netwerk/ipc/DocumentLoadListener.cpp

Audit Directions

  • Content-supplied fields on IPC load state
    Audit every field of nsDocShellLoadState deserialized from a child process for a parent-only invariant and confirm each is validated before use.
  • Override and privilege-bearing options
    Review other override or process-selection knobs (remote type, principal, sandbox flags) to ensure they cannot be set by a mTriggeringRemoteType != NOT_REMOTE_TYPE sender.
  • Session-history and about:blank shortcuts
    Trace load paths that special-case about:blank or session-history restores, since these bypassed the primary guard and may harbor similar trust gaps.
diff --git a/docshell/base/nsDocShellLoadState.cpp b/docshell/base/nsDocShellLoadState.cpp
index ea31776de66..12b11965ab4 100644
--- a/docshell/base/nsDocShellLoadState.cpp
+++ b/docshell/base/nsDocShellLoadState.cpp
@@ -162,10 +162,16 @@ nsDocShellLoadState::nsDocShellLoadState(
       return;
     }
 
-    if (mURI->SchemeIs("javascript") &&
-        mTriggeringRemoteType != NOT_REMOTE_TYPE) {
-      aActor->FatalError("Illegal cross-process javascript: load attempt");
-      return;
+    if (mTriggeringRemoteType != NOT_REMOTE_TYPE) {
+      if (mURI->SchemeIs("javascript")) {
+        aActor->FatalError("Illegal cross-process javascript: load attempt");
+        return;
+      }
+
+      if (mRemoteTypeOverride.isSome()) {
+        aActor->FatalError("RemoteTypeOverride can only be set by parent");
+        return;
+      }
     }
 
     if (!ValidatePrincipalCouldPotentiallyBeLoadedBy(
diff --git a/netwerk/ipc/DocumentLoadListener.cpp b/netwerk/ipc/DocumentLoadListener.cpp
index 9005281a3ae..845b82753d7 100644
--- a/netwerk/ipc/DocumentLoadListener.cpp
+++ b/netwerk/ipc/DocumentLoadListener.cpp
@@ -775,7 +775,9 @@ auto DocumentLoadListener::Open(nsDocShellLoadState* aLoadState,
 
   if (aLoadState->GetRemoteTypeOverride()) {
     if (!mIsDocumentLoad || !NS_IsAboutBlank(aLoadState->URI()) ||
-        !loadingContext->IsTopContent()) {
+        !loadingContext->IsTopContent() ||
+        aLoadState->GetEffectiveTriggeringRemoteType() != NOT_REMOTE_TYPE ||
+        aLoadState->LoadIsFromSessionHistory()) {
       LOG(
           ("DocumentLoadListener::Open with invalid remoteTypeOverride "
            "[this=%p]",
Loading diff…