A compromised content process can overwrite any cross-process document's Permissions-Policy
Categories
(Core :: DOM: Security, defect)
Tracking
()
People
(Reporter: vanirxxx, Unassigned)
Details
(Keywords: reporter-external, Whiteboard: [client-bounty-form])
Attachments
(1 file)
|
15.73 KB,
application/x-zip-compressed
|
Details |
Summary
ContentParent::RecvSetContainerFeaturePolicy applies a container (iframe) Permissions-Policy to any BrowsingContext a content process names, with no check that the sender hosts that context's embedder. A compromised content process can therefore grant any cross-process frame a capability (camera / microphone / geolocation / display-capture / fullscreen / …) its real embedder never delegated. Demonstrated end-to-end at runtime: a cross-origin subframe whose Permissions-Policy denies camera overwrites its own container policy and then getUserMedia succeeds.
Affects
- Component: Core :: DOM: Security (Permissions Policy)
- Version: Nightly 154.0a1, BuildID 20260703100435, changeset
6a252a86c5bb - OS: All (tested Linux)
Root cause
Defect — unauthenticated container-policy write (ContentParent::RecvSetContainerFeaturePolicy, dom/ipc/ContentParent.cpp:7869):
mozilla::ipc::IPCResult ContentParent::RecvSetContainerFeaturePolicy(
const MaybeDiscardedBrowsingContext& aContainerContext,
MaybeFeaturePolicyInfo&& aContainerFeaturePolicyInfo) {
if (aContainerContext.IsNullOrDiscarded()) {
return IPC_OK();
}
auto* context = aContainerContext.get_canonical();
context->SetContainerFeaturePolicy(std::move(aContainerFeaturePolicyInfo)); // no sender check
return IPC_OK();
}
The only guard on this async PContent message is IsNullOrDiscarded(). It never checks that the sending ContentParent hosts the embedder of aContainerContext; the attacker supplies both the target BC id (a small integer replicated into every process, not a secret) and the policy. The write is a plain member store — CanonicalBrowsingContext::SetContainerFeaturePolicy (docshell/base/CanonicalBrowsingContext.cpp:3241): mContainerFeaturePolicyInfo = std::move(...) — so this bespoke IPC bypasses the synced-BrowsingContext CheckOnlyOwningProcessCanSet authorization that guards every other cross-process BC field. The only legitimate caller, HTMLIFrameElement, sends only its own child iframe's policy; the parent never enforces that invariant.
Reaching the capability: on the victim frame's next load, DocumentLoadListener copies the stored policy into the LoadInfo (netwerk/ipc/DocumentLoadListener.cpp:2119), and Document::InitFeaturePolicy → FeaturePolicy::InheritPolicy (dom/base/Document.cpp:4070, dom/security/featurepolicy/FeaturePolicy.cpp:73) honors the attacker's mAttributeEnabledFeatureNames with an empty mInheritedDeniedFeatureNames — so an undelegated feature becomes allowed for the subframe's own origin.
Steps to Reproduce
Threat model: a compromised content process (renderer-compromise); a real renderer sends the SetContainerFeaturePolicy PContent message directly.
- Top page V (
http://v.test) embeds cross-origin subframe A (<iframe src="http://a.test/sub.html">, noallow="camera"). Under Fission, A is in its own process. - A (compromised) sends
SetContainerFeaturePolicy(A's own BC, {mAttributeEnabledFeatureNames:[camera,microphone], mInheritedDeniedFeatureNames:[], origins:A}). - A reloads, then calls
navigator.mediaDevices.getUserMedia({video:true}).
Actual Results
ASAN build, 2-site Fission harness, media.navigator.streams.fake=true:
POC_SUB BASELINE_GUM_FAIL NotAllowedError ... <- Permissions-Policy blocks camera (undelegated)
POC_FPFORGE sent SetContainerFeaturePolicy(camera,microphone) for own cross-process BC id=4294967297
POC_SUB ATTACK_GUM_OK tracks=1 label=Default Video Device <- after forge, getUserMedia SUCCEEDS
Before the forge, A's getUserMedia is rejected by Permissions-Policy; after A overwrites its own container policy via the unauthenticated IPC, it obtains a live camera track — a capability its real embedder never granted. Standalone impact: an undelegated frame can also silently enter fullscreen (not prompt-gated). Because the parent attributes a subframe's getUserMedia to the top origin (browser/actors/WebRTCParent.sys.mjs:95: data.origin = topWindowContext.documentPrincipal.origin), the same primitive composes into silent cross-origin camera/microphone capture under the top page's existing grant — this composition is code-traced, not exercised by the PoC.
Expected Results
A content process must not set the container Permissions-Policy of a BrowsingContext whose embedder it does not host.
PoC / harness details
poc/: top/sub pages (web/top.html, web/sub.html), driver (go2.sh), and poc.patch. The patch sends the forged SetContainerFeaturePolicy PContent message from the subframe's own process when the page calls window.dump("FORGE_FP"), gated on env MOZ_POC_FPTAMPER (dom/base/nsGlobalWindowInner.cpp) — standing in for a compromised renderer forging the IPC. The parent handler under test is unmodified. Verbatim run: poc/run.log.
Fix
In RecvSetContainerFeaturePolicy, verify the sending ContentParent hosts the embedder of aContainerContext before applying the policy — mirroring the CheckOnlyOwningProcessCanSet authorization used for every other synced BrowsingContext field.
Updated•4 months ago
|
Updated•3 months ago
|
Updated•2 months ago
|
Description
•