Missing principal validation in RecvPServiceWorkerRegistrationConstructor enables cross-origin notification theft
Categories
(Core :: DOM: Service Workers, defect)
Tracking
()
People
(Reporter: med.mbarek.isi, Unassigned)
Details
(Keywords: reporter-external)
Attachments
(5 files)
Summary
Missing principal validation in RecvPServiceWorkerRegistrationConstructor allows a compromised content process to forge a PServiceWorkerRegistration actor targeting another origin, enabling cross-origin notification data theft and service worker lifecycle manipulation.
Root Cause
BackgroundParentImpl::RecvPServiceWorkerRegistrationConstructor (ipc/glue/BackgroundParentImpl.cpp, line 1343) accepts both IPCServiceWorkerRegistrationDescriptor and IPCClientInfo from the content process without validating that the claimed principal matches the process's remote type.
Bug 2045721 (landed 2026-07-21) added ClientIsValidPrincipalInfo checks to other PBackground handlers (e.g., RecvCreateWebTransportParent at line 501) but missed RecvPServiceWorkerRegistrationConstructor.
Vulnerable code
// ipc/glue/BackgroundParentImpl.cpp:1342-1356
mozilla::ipc::IPCResult
BackgroundParentImpl::RecvPServiceWorkerRegistrationConstructor(
PServiceWorkerRegistrationParent* aActor,
const IPCServiceWorkerRegistrationDescriptor& aDescriptor,
const IPCClientInfo& aForClient) {
// NO ClientIsValidPrincipalInfo check — contrast with line 501
dom::InitServiceWorkerRegistrationParent(aActor, aDescriptor, aForClient);
return IPC_OK();
}
Compare with the protected handler (same file, line 494):
// RecvCreateWebTransportParent — HAS the check (added by Bug 2045721)
if (!dom::ClientIsValidPrincipalInfo(aClientInfo.principalInfo(),
BackgroundParent::GetRemoteType(this))) {
return IPC_FAIL(this, "...");
}
Impact
A compromised content process (e.g., webIsolated=http://attacker.com) can send a forged PServiceWorkerRegistration constructor message claiming to be any other origin (e.g., http://victim.com). The parent process binds the actor to the victim's real service worker registration, granting the attacker:
-
Cross-origin notification theft:
getNotifications()reads the victim's stored notifications (title, body, tag, icon, structured data) viaServiceWorkerRegistrationProxy::GetNotifications→GetStoredNotificationsForScope(forged_principal, ...). No authorization check exists in the notification storage path. -
Service worker unregistration:
Unregister()callsswm->Unregister(mReg->Principal(), ...)using the victim's real registration, allowing the attacker to remove the victim's service worker. -
Forced service worker update:
Update()triggers an update check on the victim's registration. -
NavigationPreload manipulation:
SetNavigationPreloadEnabled/Header()modifies the victim's navigation preload settings.
All of these use mReg (the victim's real ServiceWorkerRegistrationInfo) or mListeningClientInfo (constructed from the forged IPCClientInfo), which are set during ServiceWorkerRegistrationProxy::Init with no validation.
Affected Versions
- Firefox Release 153.0.1
- Firefox Beta 154.0b4
- Firefox ESR 153.0esr
- Firefox Nightly 155.0a1
Bug 2045721 landed after the release branch cut (merge date: 2026-07-20) and only fixed other handlers.
Steps to Reproduce
This is an IPC-level vulnerability. The PoC patches Firefox source code to simulate what a compromised renderer would do (sending a forged IPC message). A real attacker with renderer compromise would manipulate the IPC channel directly.
Setup
- Apply the attached patch (
poc_patch.diff) to mozilla-central - Build with
--enable-debug --disable-optimize - Start two HTTP servers:
python3 -m http.server 8081servingvictim/directory (index.html + sw.js)python3 -m http.server 8080servingattacker/directory (poc.html + sw.js)
- Create a profile with prefs:
permissions.default.desktop-notification=1,dom.serviceWorkers.enabled=true,dom.serviceWorkers.testing.enabled=true,devtools.console.stdout.content=true - Launch Firefox with both tabs:
firefox http://localhost:8081/ http://localhost:8080/poc.html - Wait ~30 seconds for the debug build to complete setup
What the source patch does
- ServiceWorkerRegistration.cpp: When origin is
localhost:8080, rewrites the outgoingPServiceWorkerRegistrationconstructor IPC to claimlocalhost:8081's principal, scope, and registration ID 1. This simulates a compromised renderer forging the IPC message. - ServiceWorkerRegistration.cpp: Bypasses a content-side
MOZ_DIAGNOSTIC_ASSERT(MatchesDescriptor(...))that is irrelevant when the renderer is compromised. - BackgroundParentImpl.cpp: Adds stderr logging to show the parent accepts the forged message without validation.
- ServiceWorkerRegistrationProxy.cpp: Adds stderr logging to show the forged principal is used to query notification storage.
Expected result
The parent process should reject the forged IPC because the claimed principal doesn't match the content process's remote type.
Actual result
The parent accepts the forged IPC. The attacker's page reads the victim's stored notification:
[PoC-JS] Notifications stolen: 1
[PoC-JS] STOLEN title="Confidential: Account Alert" body="Your balance is $50,000. Transfer code: XYZZY-SECRET-42" tag="account-alert"
[PoC-JS] STOLEN data={"account":"1234-5678-9012","pin":"9999","ssn":"123-45-6789"}
[PoC-JS] === BUG CONFIRMED === Cross-origin notification theft successful
Parent-side stderr confirms the missing check:
[PoC] FORGED PServiceWorkerRegistration: http://localhost:8080 -> http://localhost:8081 (id=1)
[PoC] RecvPServiceWorkerRegistrationConstructor: remoteType=webIsolated=http://localhost scope=http://localhost:8081/ id=1 — NO ClientIsValidPrincipalInfo check!
[PoC] GetNotifications: reading notifications for origin=http://localhost:8081 scope=http://localhost:8081/
Suggested Fix
Add ClientIsValidPrincipalInfo and ValidatePrincipalInfo checks to RecvPServiceWorkerRegistrationConstructor, matching the pattern from Bug 2045721:
mozilla::ipc::IPCResult
BackgroundParentImpl::RecvPServiceWorkerRegistrationConstructor(
PServiceWorkerRegistrationParent* aActor,
const IPCServiceWorkerRegistrationDescriptor& aDescriptor,
const IPCClientInfo& aForClient) {
if (!dom::ClientIsValidPrincipalInfo(aForClient.principalInfo(),
BackgroundParent::GetRemoteType(this))) {
return IPC_FAIL(this,
"PServiceWorkerRegistration ClientInfo principal not valid for remote type");
}
if (!BackgroundParent::ValidatePrincipalInfo(this, aDescriptor.principalInfo())) {
return IPC_FAIL(this,
"PServiceWorkerRegistration descriptor principal is invalid");
}
dom::InitServiceWorkerRegistrationParent(aActor, aDescriptor, aForClient);
return IPC_OK();
}
| Reporter | ||
Comment 1•3 months ago
|
||
| Reporter | ||
Comment 2•3 months ago
|
||
| Reporter | ||
Comment 3•3 months ago
|
||
| Reporter | ||
Comment 4•3 months ago
|
||
| Reporter | ||
Comment 5•3 months ago
|
||
Comment 6•3 months ago
|
||
Thank you for the report. This issue has been determined to be a duplicate of bug 2054667.
This bug is the same underlying vulnerability as the canonical bug 2054667:
- Same handler:
BackgroundParentImpl::RecvPServiceWorkerRegistrationConstructor - Same root cause: no validation of the child-supplied
IPCServiceWorkerRegistrationDescriptor/IPCClientInfoprincipals against the sending process - Same impact: cross-origin notification theft via
GetNotifications, plus Unregister/Update/navigation-preload tampering
Updated•10 days ago
|
Description
•