BackgroundParentImpl::RecvPBroadcastChannelConstructor accepts content-supplied PrincipalInfo without cross-process authorization
Categories
(Core :: DOM: postMessage, defect)
Tracking
()
People
(Reporter: atsu120225, Unassigned)
Details
(Keywords: reporter-external, Whiteboard: [client-bounty-form])
Attachments
(2 files)
Summary
BackgroundParentImpl::RecvPBroadcastChannelConstructor at ipc/glue/BackgroundParentImpl.cpp:795 accepts a content-supplied const PrincipalInfo& plus an nsACString aOrigin and binds a PBroadcastChannelParent for the specified origin. Its only principal check dispatches CheckPrincipalRunnable to the main thread asynchronously. That runnable verifies only that the child's supplied aPrincipalInfo and aOrigin values are self-consistent with both values attacker-chosen. It never consults whether the calling content process is authorized for the claimed origin. A compromised content process joins and can send on any victim origin's BroadcastChannel bus.
The in-tree comment at ipc/glue/BackgroundParentImpl.cpp:809 acknowledges the gap verbatim: // XXX The principal can be checked right here on the PBackground thread since BackgroundParentImpl now overrides the ProcessingError method and kills invalid child processes.
Discovered by the static-analysis checker submitted as Bug 2034872.
Affected
Product Core, Component IPC, Version Trunk mozilla-central. Severity S3, sec-moderate conditional on a compromised content process, same class as CVE-2026-6760. Keywords sec-moderate, sec-audit.
Recv entry at ipc/glue/BackgroundParentImpl.cpp:795. CheckPrincipalRunnable at ipc/glue/BackgroundParentImpl.cpp:742-791, with the self-consistency check at L779 where mOrigin.Equals(origin) compares two attacker-supplied values. The in-tree XXX comment acknowledging the known gap is at ipc/glue/BackgroundParentImpl.cpp:809-811.
Steps to reproduce
An attacker runs a compromised content process with IPC-send capability to the parent PBackground channel. The child sends RecvPBroadcastChannelConstructor with actor a child-allocated PBroadcastChannelParent stub, aPrincipalInfo a well-formed ContentPrincipalInfo for https://victim.example, aOrigin the matching string https://victim.example, and aChannel the channel name to join. The attacker sets both aPrincipalInfo and aOrigin so they agree. The parent returns IPC_OK() immediately after dispatching the async CheckPrincipalRunnable. The PBroadcastChannelParent actor is bound on the PBackground thread before the runnable runs. CheckPrincipalRunnable::Run() on the main thread converts the child-supplied aPrincipalInfo to a principal, extracts its origin, compares it to the child-supplied aOrigin, finds both equal to https://victim.example, passes the check, and does not call KillHard. The child's PBroadcastChannelParent actor remains bound and joined to the victim's BroadcastChannel bus. Subsequent PBroadcastChannel::Post and message-received events carry the victim origin's BroadcastChannel traffic to the attacker and allow the attacker to inject messages that any legitimate victim-origin listener receives.
Empirical evidence
Build rev mozilla-central. gtest installed at dom/fs/test/gtest/parent/TestBroadcastChannelIPCPrimitive.cpp.
./mach gtest 'BroadcastChannelIPCPrimitive.*'
BroadcastChannelIPCPrimitive.SelfConsistencyCheckPassesForForgery PASS
BroadcastChannelIPCPrimitive.SelfConsistencyAcceptsHighValueTarget PASS
BroadcastChannelIPCPrimitive.CheckOnlyCatchesSelfInflictedMismatch PASS
Ran 3 test(s) from 1 test suite(s) (0 disabled), with 0 failure(s).
Each test exercises the exact predicate from CheckPrincipalRunnable::Run, PrincipalInfoToPrincipal(PI)->GetOrigin() == aOrigin with attacker-chosen matching values, demonstrating that the check returns success for any victim origin the attacker selects.
Root cause
The Recv at L795
mozilla::ipc::IPCResult BackgroundParentImpl::RecvPBroadcastChannelConstructor(
PBroadcastChannelParent* actor, const PrincipalInfo& aPrincipalInfo,
const nsACString& aOrigin, const nsAString& aChannel) {
AssertIsInMainProcess();
AssertIsOnBackgroundThread();
RefPtr<ThreadsafeContentParentHandle> parent =
BackgroundParent::GetContentParentHandle(this);
if (!parent) { return IPC_OK(); }
// XXX The principal can be checked right here on the PBackground thread
// since BackgroundParentImpl now overrides the ProcessingError method and
// kills invalid child processes (IPC_FAIL triggers a processing error).
RefPtr<CheckPrincipalRunnable> runnable =
new CheckPrincipalRunnable(parent.forget(), aPrincipalInfo, aOrigin);
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable));
return IPC_OK();
}
Two independent gaps. First, the only check performed is PrincipalInfoToPrincipal(aPrincipalInfo)->GetOrigin() == aOrigin with both aPrincipalInfo and aOrigin supplied by the child; a child crafting matching values passes trivially. No consultation of the ContentParent for origin-to-child authorization occurs. Second, the check is async on the main thread. By the time it runs, the Recv has already returned IPC_OK() and the PBroadcastChannelParent actor is bound on the PBackground thread. Any messages the child sends during the window are processed. The XXX comment in the code itself prescribes moving the check inline on the PBackground thread.
Security impact
The primitive is cross-origin BroadcastChannel join plus message inject and sniff on the victim origin, conditional on a compromised content process with PBackground IPC-send capability. Victim-origin BroadcastChannel traffic may carry session state, login events, cross-tab sync data, draft content, and notification payloads; confidentiality breaks wholesale. The attacker also injects BroadcastChannel messages that legitimate victim-origin listeners treat as same-origin trusted data, breaking integrity. Reachability extends to any service worker or tab at the victim origin listening on the named channel.
Relationship to Bug 2022576 and Bug 2034872
Same defect class as CVE-2026-6760. Survival odds for this instance are elevated by the in-tree XXX comment: the original code author flagged the gap and deferred the fix rather than missing it. The CheckPrincipalRunnable async dispatch also masks the defect from grep-based sweeps that look for a direct fence call in the Recv body. Discovered by the clang-query checker in Bug 2034872, which enumerated this handler among 40 TPs across 10 files. First-filing likely.
Mitigation
Replace the async dispatch with an inline check on the PBackground thread as the in-tree XXX comment prescribes
if (BackgroundParent::IsOtherProcessActor(this)) {
if (!ContentParent::CheckContentProcessSecurity(parent, aPrincipalInfo)) {
return IPC_FAIL_NO_REASON(actor);
}
}
Inline ContentParent-to-origin verification is the correct fix. The existing CheckPrincipalRunnable self-consistency check adds no security value beyond catching child implementation bugs and should be removed or subordinated to the ownership check.
| Reporter | ||
Comment 1•6 months ago
|
||
Updated•6 months ago
|
Updated•6 months ago
|
Updated•1 month ago
|
Description
•