
Firefox content-to-parent IPDL privilege escalation (N-day, bug 2054416): forged PDocumentChannel with RemoteTypeOverride -> privilegedabout process placement, via mojo-port send-path injection from a compromised content process
Proof of concept for a sandbox escape in Firefox 149.0a1 nightly (2fbc0748c4,
pre-fix). A compromised content process forges an IPDL message that makes the
parent load a page into a privileged (privilegedabout) process.
This is the stage-2 sandbox escape, designed to chain after a content-process RCE (e.g. CVE-2026-2796).
The parent process deserializes a nsDocShellLoadState from content→parent IPDL
messages. The vulnerable build does not reject a content-set RemoteTypeOverride
field, so content can choose which process type a navigation lands in.
The fix (commit 3ec5c71b41bb, "Validate RemoteTypeOverride") makes the
nsDocShellLoadState ctor reject any override on a content-triggered load.
When the override is honored — document load of about:blank, top-level context —
IsolationOptionsForNavigation sets the load's remote type to the override value,
and the page is placed in a privileged process. Sandbox boundary violated.
The override rides on PNecko::PDocumentChannel, a message every content process
sends for each document load (DocumentChannelCreationArgs.loadState is a
nsDocShellLoadState).
Delivery is the interesting part: raw writes to the IPC socket are silently dropped, because IPDL messages now ride mojo ports. Instead, the PoC drives the real send path from JavaScript via native calls (using the CVE-2026-2796 funcref primitive):
operator new — allocate a heap block for the IPC::MessageIPC::Message::Message(routing, type, capacity, flags) — placement ctorPickle::WriteBytes — append the forged payloadMessageChannel::Send on the PContent channel — mojo framing, port routing,
and sequence numbers are all handled nativelyThe payload (built by forge.py) carries RemoteTypeOverride = "privilegedabout",
URI about:blank, a null principal, and runtime-leaked values (BrowsingContext id,
TriggeringRemoteType).
./irun
Parent-side logs (MOZ_LOG=ProcessIsolation:5,DocumentChannel:5) show the
process switch into the privileged process:
V/ProcessIsolation using remote type override (privilegedabout) for load
I/ProcessIsolation Process Switch: Changing Remoteness from 'webIsolated=http://127.0.0.1' to 'privilegedabout'
3ec5c71b41bb "Validate RemoteTypeOverride"| File | Purpose |
|---|
privesc.html | PoC page, run inside the compromised content process |
forge.py / forge.json | build the forged IPDL message (self-verifying) |
wasm-bytes.js | stage-1 primitives: arb R/W, funcref calls |
mdrive.py / mdrive2.py | marionette harness to launch and drive Nightly |
irun | instrumented run: lldb attach to parent + MOZ_LOG evidence |
profile.user.js | Firefox profile prefs (fission on, dump enabled) |
parse_dc.py | byte-exact parser for captured DocumentChannel messages |
scan_sentinels.py / extract_fields.py | field-map scanners for captures |