Closed Bug 2023074 Opened 8 months ago Closed 8 months ago

BroadcastChannelParent: circular principal validation allows cross-origin channel join from compromised content process

Categories

(Core :: DOM: postMessage, defect)

defect

Tracking

()

RESOLVED DUPLICATE of bug 2016925

People

(Reporter: lukefrancis09, Unassigned)

Details

(Keywords: ai-involved, reporter-external, Whiteboard: [client-bounty-form])

A compromised content process can join any origin's BroadcastChannel. The PBroadcastChannelConstructor handler registers the actor before principal validation runs, and the validation itself is circular — it checks the attacker-supplied PrincipalInfo against the attacker-supplied origin string. Since the attacker controls both, the check always passes. ValidatePrincipal is never called.

Bug:

Registration before validation — BroadcastChannelParent's constructor (dom/broadcastchannel/BroadcastChannelParent.cpp:21-27) calls mService->RegisterActor(this, mOriginChannelKey) immediately. The actor is live and receiving messages before RecvPBroadcastChannelConstructor even dispatches the async principal check.

Circular check — CheckPrincipalRunnable::Run() converts the IPC-supplied mPrincipalInfo to a principal, extracts its origin, and compares it to the IPC-supplied mOrigin. Both values come from the content process. A compromised process constructs a valid ContentPrincipalInfo for any target origin and sets aOrigin to match — the check passes trivially.

The code at BackgroundParentImpl.cpp line 802-804 contains a TODO acknowledging this should be synchronous.

No process identity check — ValidatePrincipal is used in ~15 other ContentParent handlers to verify that a content process is authorized to claim a given principal. It is completely absent from the BroadcastChannel path. BackgroundParent::GetContentParentHandle(this) is available but unused.

Delivery — BroadcastChannelService::PostMessage routes messages by originChannelKey match only. No origin check at delivery time.

Attack Scenario

  1. Compromised content process (evil.com) sends PBroadcastChannelConstructor with aPrincipalInfo = ContentPrincipalInfo{spec="https://bank.com/"}, aOrigin = "https://bank.com", aChannel = "session-sync"
  2. Parent creates actor, constructor registers it — actor is live
  3. CheckPrincipalRunnable dispatches async, converts PrincipalInfo → origin "https://bank.com", compares with aOrigin "https://bank.com" → passes
  4. Attacker receives all messages on bank.com's "session-sync" channel and can inject messages

Impact

Cross-origin BroadcastChannel eavesdropping and injection. Applications using BroadcastChannel for cross-tab session sync, auth state, or coordination are exposed. Bypasses Fission's process-per-site isolation at the BroadcastChannel layer — the process doesn't need to host the target origin, it just claims to.

Suggested Fix

  1. Move RegisterActor() from the constructor to a callback after principal validation succeeds
  2. Replace the circular origin-string check with ValidatePrincipal (or ValidatePrincipalCouldPotentiallyBeLoadedBy) using the ContentParent associated with the BackgroundParentImpl

let me know if you need any additional information, happy to provide it!

Flags: sec-bounty?
Group: firefox-core-security → dom-core-security
Status: UNCONFIRMED → RESOLVED
Closed: 8 months ago
Component: Security → DOM: postMessage
Duplicate of bug: 2016925
Product: Firefox → Core
Resolution: --- → DUPLICATE
Flags: sec-bounty? → sec-bounty-
Group: dom-core-security
You need to log in before you can comment on or make changes to this bug.