RecvPBroadcastChannelConstructor async principal check only verifies internal consistency of two attacker-controlled values — compromised renderer joins any origin's BroadcastChannel
Categories
(Core :: DOM: postMessage, defect)
Tracking
()
People
(Reporter: pakhunov.anton.n, Unassigned)
Details
(Keywords: ai-involved, reporter-external)
While auditing PBackground actor constructors I found that RecvPBroadcastChannelConstructor (ipc/glue/BackgroundParentImpl.cpp ~line 789) dispatches an async CheckPrincipalRunnable to validate the caller, but the check only verifies that the IPC-supplied aPrincipalInfo and aOrigin are internally consistent with each other. Since the attacker controls both, a consistent pair for any target origin trivially passes.
The actor is registered in the shared BroadcastChannelService singleton in its constructor — before the async check fires:
// AllocPBroadcastChannelParent — actor registered immediately with attacker-controlled origin key
BackgroundParentImpl::AllocPBroadcastChannelParent(
const PrincipalInfo& aPrincipalInfo, const nsACString& aOrigin, ...) {
nsString originChannelKey;
originChannelKey.Assign(aChannel);
originChannelKey.AppendLiteral("|");
originChannelKey.Append(NS_ConvertUTF8toUTF16(aOrigin)); // attacker-controlled
return new BroadcastChannelParent(originChannelKey); // registers in BroadcastChannelService
}
// RecvPBroadcastChannelConstructor — async check only checks consistency of two IPC values
mozilla::ipc::IPCResult BackgroundParentImpl::RecvPBroadcastChannelConstructor(
PBroadcastChannelParent* actor, const PrincipalInfo& aPrincipalInfo,
const nsACString& aOrigin, ...) {
// ...
RefPtr<CheckPrincipalRunnable> runnable =
new CheckPrincipalRunnable(parent.forget(), aPrincipalInfo, aOrigin);
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable));
return IPC_OK(); // actor is already live in mAgents
}
CheckPrincipalRunnable::Run() (lines 751-778):
auto principalOrErr = PrincipalInfoToPrincipal(mPrincipalInfo); // from IPC
// ...
principalOrErr.unwrap()->GetOrigin(origin); // derived from IPC spec
if (NS_WARN_IF(!mOrigin.Equals(origin))) { // mOrigin also from IPC
contentParent->KillHard(...);
}
// If attacker sends consistent pair (spec="https://bank.com/", aOrigin="https://bank.com") -> PASSES
BroadcastChannelService is a process-level singleton with a single mAgents hashmap shared across all content process connections. Once registered, the malicious actor receives every PostMessage call for the claimed origin+channel key.
Attack (compromised evil.com renderer targeting bank.com):
- Send PBroadcastChannelConstructor with:
aPrincipalInfo = ContentPrincipalInfo{ spec: "https://bank.com/", originNoSuffix: "https://bank.com", attrs: {} }
aOrigin = "https://bank.com"
aChannel = "sessionSync" (known or guessed) - AllocPBroadcastChannelParent registers actor under key "sessionSync|https://bank.com" in mAgents.
- CheckPrincipalRunnable: GetOrigin(principal) == "https://bank.com" == mOrigin -> PASSES, process not killed.
- Malicious actor permanently receives all BroadcastChannel("sessionSync") messages from bank.com tabs.
- Can also inject messages: all bank.com tabs receive them as same-origin.
Impact: cross-origin BroadcastChannel eavesdropping and message spoofing, breaking site isolation. Requires compromised renderer.
The fix is to call ContentParent::ValidatePrincipal (ValidatePrincipalCouldPotentiallyBeLoadedBy) on the IPC-supplied principal — as done for clipboard, notifications, and initial window initialization — either synchronously in RecvPBroadcastChannelConstructor or by deferring RegisterActor until the authorization check completes.
Firefox version: mozilla-central (trunk)
OS: all platforms
Updated•7 months ago
|
Updated•7 months ago
|
Updated•1 month ago
|
Description
•