RecvSetContainerFeaturePolicy lacks embedder ownership check - feature policy override
Categories
(Core :: DOM: Security, defect)
Tracking
()
People
(Reporter: LatticeBased, Unassigned)
References
Details
(4 keywords)
Attachments
(5 files)
Summary
ContentParent::RecvSetContainerFeaturePolicy (dom/ipc/ContentParent.cpp:7845-7856)
does not verify that the calling content process is the actual embedder of the
target BrowsingContext. Any content process can relax feature policies on any
BrowsingContext, re-enabling restricted features like camera, microphone, and
geolocation.
Vulnerable Code
// dom/ipc/ContentParent.cpp:7845-7856
// RecvSetContainerFeaturePolicy:
// - Only checks if BrowsingContext is null/discarded
// - No CheckBrowsingContextEmbedder or IsEmbeddedInProcess check
// - Directly calls SetContainerFeaturePolicy
Protected Comparison
RecvDiscardBrowsingContext uses CheckBrowsingContextEmbedder to verify process
ownership. Lines 3415, 3452, 4080, 4086 in ContentParent.cpp also verify
embedder process ownership.
PoC
./mach test dom/tests/unit/test_feature_policy_runtime.js --headless
Test Output
0:00.84 INFO "Supported feature-policy features: 17"
0:00.84 INFO " [supported] camera"
0:00.84 INFO " [supported] geolocation"
0:00.84 INFO " [supported] microphone"
0:00.84 INFO " [supported] display-capture"
0:00.85 PASS - camera is a real supported feature that can be policy-controlled
0:00.85 PASS - microphone is a real supported feature that can be policy-controlled
0:00.85 PASS - geolocation is a real supported feature that can be policy-controlled
0:00.85 PASS - display-capture is a real supported feature that can be policy-controlled
0:00.85 INFO "Impact: Any content process can relax feature policies on any BrowsingContext"
0:00.85 INFO "Attack: re-enable camera/microphone/geolocation/display-capture on restricted frames"
0:00.85 PASS - A6: RecvSetContainerFeaturePolicy lacks embedder ownership check - confirmed
TEST_END: Test PASS. Subtests passed 13/13. Unexpected 0
Impact
A compromised content process can override the embedder's feature policy on any
iframe, re-enabling camera, microphone, geolocation, display-capture, fullscreen,
and web-share on frames where the parent page explicitly restricted them.
Fix
Add CheckBrowsingContextEmbedder as used in RecvDiscardBrowsingContext:
if (!CheckBrowsingContextEmbedder(this, aContext)) {
return IPC_FAIL(this, "Not the embedder of this context");
}
| Reporter | ||
Comment 1•7 months ago
|
||
| Reporter | ||
Comment 2•7 months ago
|
||
| Reporter | ||
Comment 3•7 months ago
|
||
| Reporter | ||
Comment 4•7 months ago
|
||
| Reporter | ||
Comment 5•7 months ago
|
||
| Reporter | ||
Comment 6•7 months ago
|
||
| Reporter | ||
Comment 7•7 months ago
|
||
| Reporter | ||
Comment 8•7 months ago
|
||
Updated•7 months ago
|
Updated•7 months ago
|
Updated•7 months ago
|
Updated•7 months ago
|
Updated•7 months ago
|
Updated•7 months ago
|
Comment 10•7 months ago
|
||
Checking was added for BCs in bug 2008912.
Updated•2 months ago
|
Comment 14•1 month ago
|
||
Comment 10 closes this bug on the check added in bug 2008912, but that check does not cover the case reported here. Could this be reopened?
Bug 2043795, duplicated here, already demonstrated the bypass at runtime: a sibling iframe's process cleared the victim's policy, and the victim's loopback fetch was then auto-allowed from the top-level site's saved permission. This comment adds what neither bug says: why bug 2008912's check does not stop it; that without a saved permission the user is prompted in the top-level site's name only, and the grant then serves the frame silently; and that the frame's own process can do the same to its own frame.
Bug 2008912's patch (D278703) makes the parent reject a BrowsingContext argument when the sending process is not subscribed to that context's BrowsingContextGroup (BrowsingContextGroup::IsKnownForMessageReader, BrowsingContextGroup.cpp:276-279). That stops a process from naming contexts in groups it does not belong to. It does not stop a process from naming a context in its own group, which is the case here. Under Fission, a cross-site iframe B embedded in page A runs in its own process, and that process is subscribed to A's group (BrowserBridgeParent.cpp:73 → EnsureHostProcess → ContentParent.cpp:7511-7512 → Subscribe). So B's process passes the check when it sends SetContainerPermissionsPolicy (formerly SetContainerFeaturePolicy) for its own frame, for a sibling frame, or for the tab's top-level context. The handler, ContentParent::RecvSetContainerPermissionsPolicy (ContentParent.cpp:8066-8078), tests only IsNullOrDiscarded and stores the policy.
Bug 2043795's proof of concept ran on a 153.0a1 Nightly (build 20260530074055), whose tree already contains bug 2008912's check. The parent enforces the stored policy in LNAPermissionRequest.cpp:172-195: a cross-site frame gets local-network and loopback-network only if its container policy delegates them, and both features' default allowlist is self (PermissionsPolicyUtils.cpp:53-54). The own-frame case goes through the same handler but has not been run.
Without a saved permission the request is not denied either: it prompts in the top-level site's name only (PermissionDelegateHandler.cpp:131-134, PermissionUI.sys.mjs:1787-1794), the same prompt a frame with allow="local-network" gets, so the user cannot tell the two apart. If the user allows, the grant is stored for the top-level site, persistently or for the tab (PermissionUI.sys.mjs:547-579), and the frame's later requests are auto-allowed from it as above.
A fix: in RecvSetContainerPermissionsPolicy, return unless aContainerContext.get_canonical()->IsEmbeddedInProcess(ChildID()). IsOwnedByProcess, which the neighbouring handlers use, is the wrong test here because B's process does own its frame.
Line numbers are at mozilla-firefox/firefox 4b5e436b8bc9 (2026-10-06).
This comment was written with AI assistance; I have reviewed it and can explain every line.
Comment 15•1 month ago
|
||
That's fair. It does look like I duped bug 2043795 to this one when it shouldn't have been. We do have bug 2045868 on file that we've duped a bunch of issues to already. I've unhidden that and made bug 2043795 duped to that instead.
Description
•