Closed Bug 2025192 Opened 7 months ago Closed 7 months ago

RecvSetContainerFeaturePolicy lacks embedder ownership check - feature policy override

Categories

(Core :: DOM: Security, defect)

defect

Tracking

()

RESOLVED INVALID

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");
}
## PoC Output (Firefox 150.0a1) ``` {"features":["camera","microphone","geolocation","fullscreen","autoplay","display-capture","web-share"],"count":7,"note":"RecvSetContainerFeaturePolicy has no CheckBrowsingContextEmbedder"} ``` Run: `pip install marionette-driver && MOZ_HEADLESS=1 python3 poc_a6_featurepolicy.py`
## PoC Output (Firefox 150.0a1) ``` {"features":["camera","microphone","geolocation","fullscreen","autoplay","display-capture","web-share"],"count":7,"note":"RecvSetContainerFeaturePolicy has no CheckBrowsingContextEmbedder"} ``` Run: `pip install marionette-driver && MOZ_HEADLESS=1 python3 poc_a6_featurepolicy.py`
## PoC Output (Firefox 150.0a1) ``` {"features":["camera","microphone","geolocation","fullscreen","autoplay","display-capture","web-share"],"count":7,"note":"RecvSetContainerFeaturePolicy has no CheckBrowsingContextEmbedder"} ``` Run: `pip install marionette-driver && MOZ_HEADLESS=1 python3 poc_a6_featurepolicy.py`
Group: core-security → dom-core-security
Severity: S2 → --
Keywords: sec-audit
Priority: P2 → --
Severity: -- → S3
Component: DOM: Content Processes → DOM: Security
Severity: S3 → --
Duplicate of this bug: 2032331

Checking was added for BCs in bug 2008912.

Status: UNCONFIRMED → RESOLVED
Closed: 7 months ago
Resolution: --- → INVALID
Duplicate of this bug: 2043795
See Also: → 2045868
Duplicate of this bug: 2038450
Duplicate of this bug: 2054530
See Also: → 2062703
Group: dom-core-security

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.

Flags: needinfo?(continuation)
No longer duplicate of this bug: 2043795

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.

Flags: needinfo?(continuation)
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: