CVE-2024-23271
Overview
Background
- Permissions/Feature Policy
- A mechanism that gates powerful features (camera, microphone, fullscreen, geolocation, payment) per embedding context, controlled by the embedder’s allow attribute or default rules.
- Embedding elements
- iframe, embed, object and frame can all host nested browsing contexts; policy must apply to all of them, not just iframe.
- defaultPolicy
- The feature policy applied when no allow attribute is present; the fix uses it for non-iframe owners.
Root Cause Analysis
This fixes a Permissions/Feature Policy enforcement gap: policy was applied only to iframe owners, not to other embedding elements. isFeaturePolicyAllowedByDocumentAndAllOwners walks from a document up to the top document and, for each ancestor, consults the owner element’s feature policy. Pre-patch it only handled the case where the owner was an HTMLIFrameElement: it did dynamicDowncast<HTMLIFrameElement>(ownerElement) and, only if that succeeded, checked iframe->featurePolicy().allows(type, origin); for any other embedding element (for example embed, object, or frame) the owner branch was skipped, so the powerful-feature gate (camera, microphone, fullscreen, geolocation, payment, etc.) was simply not enforced for documents embedded that way. A page could therefore host cross-origin content in a non-iframe embedder and use gated capabilities that Permissions Policy should deny — unexpected cross-origin behavior.
The fix computes isAllowedByFeaturePolicy for BOTH cases: iframe owners use iframe->featurePolicy().allows(…), and any other owner element falls back to FeaturePolicy::defaultPolicy(ownerElement->document()).allows(…), i.e. the default (no allow attribute) rules are applied to embed/frame owners; if neither allows it, the check fails. FeaturePolicy::parse is also changed to take the iframe by pointer (nullable) so it can be called for non-iframe owners, and the failure log message changes from ‘for iframe’ to ‘for element’.
The restored invariant is that feature/permissions policy is enforced for every embedding element, not just iframes.
The added test exercises getUserMedia inside an embed element.
Attack Path
- Embed cross-origin content without an iframe Host cross-origin content via a non-iframe embedder (embed/object/frame) rather than an iframe.
- Skip the policy gate isFeaturePolicyAllowedByDocumentAndAllOwners only checked iframe owners, so the embedded document’s ancestor check is bypassed.
- Use a gated feature Invoke a permissions-policy-gated capability (camera, microphone, fullscreen, geolocation, payment) that should be denied cross-origin.
- Obtain cross-origin access The feature is granted despite policy, yielding unexpected cross-origin behavior / access to powerful features.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
isFeaturePolicyAllowedByDocumentAndAllOwnersSource/WebCore/html/FeaturePolicy.cpp |
modified | Applies the owner's feature policy for iframe owners and FeaturePolicy::defaultPolicy(...) for any other embedding element, instead of only checking HTMLIFrameElement owners. |
FeaturePolicy::parseSource/WebCore/html/FeaturePolicy.cpp |
modified | Takes the iframe as a nullable pointer so the default policy can be parsed/applied for non-iframe owners. |
FeaturePolicy::defaultPolicy / parse (declarations)Source/WebCore/html/FeaturePolicy.h |
modified | Exposes a default-policy path usable for embed/frame owners. |
Files Changed
LayoutTests/fullscreen/full-screen-enabled-expected.txtLayoutTests/fullscreen/full-screen-enabled-prefixed-expected.txtLayoutTests/fullscreen/full-screen-iframe-not-allowed-expected.txtLayoutTests/fullscreen/full-screen-iframe-without-allow-attribute-allowed-from-parent-expected.txtLayoutTests/fullscreen/full-screen-restrictions-expected.txtLayoutTests/http/tests/fullscreen/fullscreen-feature-policy-expected.txtLayoutTests/http/tests/gamepad/gamepad-allow-attribute.https-expected.txtLayoutTests/http/tests/media/media-stream/enumerate-devices-iframe-allow-attribute-expected.txtLayoutTests/http/tests/media/media-stream/get-user-media-in-embed-element-expected.txtLayoutTests/http/tests/media/media-stream/get-user-media-in-embed-element.htmlLayoutTests/http/tests/media/media-stream/resources/get-user-media-embed.htmlLayoutTests/http/tests/paymentrequest/payment-allow-attribute.https-expected.txtLayoutTests/http/tests/security/sandboxed-iframe-geolocation-getCurrentPosition-expected.txtLayoutTests/http/tests/security/sandboxed-iframe-geolocation-watchPosition-expected.txtLayoutTests/http/tests/ssl/media-stream/get-user-media-different-host-expected.txtLayoutTests/http/tests/ssl/media-stream/get-user-media-nested-expected.txtLayoutTests/http/tests/webrtc/enumerateDevicesInFrames-expected.txtLayoutTests/http/tests/webshare/webshare-allow-attribute-canShare.https-expected.txtLayoutTests/http/tests/webshare/webshare-allow-attribute-share.https-expected.txtLayoutTests/imported/w3c/web-platform-tests/html/semantics/embedded-content/the-iframe-element/iframe-allow-expected.txtLayoutTests/imported/w3c/web-platform-tests/html/semantics/embedded-content/the-iframe-element/iframe-allowfullscreen-expected.txtLayoutTests/imported/w3c/web-platform-tests/mediacapture-streams/MediaStream-feature-policy-none.https-expected.txtLayoutTests/imported/w3c/web-platform-tests/permissions-policy/payment-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub-expected.txtLayoutTests/imported/w3c/web-platform-tests/screen-wake-lock/wakelock-enabled-by-feature-policy-attribute-redirect-on-load.https.sub-expected.txtLayoutTests/imported/w3c/web-platform-tests/web-share/disabled-by-permissions-policy-cross-origin.https.sub-expected.txtLayoutTests/platform/glib/imported/w3c/web-platform-tests/mediacapture-streams/MediaStream-feature-policy-none.https-expected.txtLayoutTests/platform/glib/imported/w3c/web-platform-tests/screen-wake-lock/wakelock-enabled-by-feature-policy-attribute-redirect-on-load.https.sub-expected.txtSource/WebCore/html/FeaturePolicy.cppSource/WebCore/html/FeaturePolicy.h
Audit Directions
- Same function: owner-type handlingAudit isFeaturePolicyAllowedByDocumentAndAllOwners and related ownerElement checks for other dynamicDowncast<HTMLIFrameElement> gates that ignore embed/object/frame owners.
- Policy checks keyed on iframeGrep WebCore for featurePolicy()/allow-attribute logic that assumes an HTMLIFrameElement owner and misses other embedding elements.
Patch
diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog
index 409133b0ed61..e0a3e7cb2553 100644
--- a/LayoutTests/ChangeLog
+++ b/LayoutTests/ChangeLog
@@ -1,3 +1,13 @@
+2020-08-18 Antti Koivisto <antti@apple.com>
+
+ The CSS specificity of :host() pseudo-classes is wrong
+ https://bugs.webkit.org/show_bug.cgi?id=202494
+ <rdar://problem/66292568>
+
+ Reviewed by Anders Carlsson.
+
+ * TestExpectations:
+
2020-08-18 Diego Pino Garcia <dpino@igalia.com>
[GTK] Unreviewed test gardening. Update test baseline after r265749.
diff --git a/LayoutTests/TestExpectations b/LayoutTests/TestExpectations
index e17f48539072..1d87e2921dd0 100644
--- a/LayoutTests/TestExpectations
+++ b/LayoutTests/TestExpectations
@@ -4420,7 +4420,6 @@ webkit.org/b/214461 imported/w3c/web-platform-tests/css/css-pseudo/spelling-erro
webkit.org/b/214462 imported/w3c/web-platform-tests/css/css-scoping/host-context-specificity-001.html [ ImageOnlyFailure ]
webkit.org/b/214462 imported/w3c/web-platform-tests/css/css-scoping/host-context-specificity-002.html [ ImageOnlyFailure ]
webkit.org/b/214462 imported/w3c/web-platform-tests/css/css-scoping/host-context-specificity-003.html [ ImageOnlyFailure ]
-webkit.org/b/214462 imported/w3c/web-platform-tests/css/css-scoping/host-specificity.html [ ImageOnlyFailure ]
webkit.org/b/214462 imported/w3c/web-platform-tests/css/css-scoping/host-with-default-namespace-001.html [ ImageOnlyFailure ]
webkit.org/b/214462 imported/w3c/web-platform-tests/css/css-scoping/shadow-directionality-001.tentative.html [ ImageOnlyFailure ]
webkit.org/b/214462 imported/w3c/web-platform-tests/css/css-scoping/shadow-directionality-002.tentative.html [ ImageOnlyFailure ]
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index 3433c7799ce4..383409fd4bc2 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,18 @@
+2020-08-18 Antti Koivisto <antti@apple.com>
+
+ The CSS specificity of :host() pseudo-classes is wrong
+ https://bugs.webkit.org/show_bug.cgi?id=202494
+ <rdar://problem/66292568>
+
+ Reviewed by Anders Carlsson.
+
+ https://drafts.csswg.org/css-scoping/#host-selector
+
+ “The specificity of :host() is that of a pseudo-class, plus the specificity of its argument.”
+
+ * css/CSSSelector.cpp:
+ (WebCore::simpleSelectorSpecificityInternal):
+
2020-08-18 Youenn Fablet <youenn@apple.com>
Add a JS built-in routine to mark a promise as handled
diff --git a/Source/WebCore/css/CSSSelector.cpp b/Source/WebCore/css/CSSSelector.cpp
index afd9bab0f9e6..c810b21d9c98 100644
--- a/Source/WebCore/css/CSSSelector.cpp
+++ b/Source/WebCore/css/CSSSelector.cpp
@@ -128,6 +128,7 @@ static unsigned simpleSelectorSpecificityInternal(const CSSSelector& simpleSelec
return 0;
case CSSSelector::PseudoClassNthChild:
case CSSSelector::PseudoClassNthLastChild:
+ case CSSSelector::PseudoClassHost:
return CSSSelector::addSpecificities(static_cast<unsigned>(SelectorSpecificityIncrement::ClassB), simpleSelector.selectorList() ? maxSpecificity(*simpleSelector.selectorList()) : 0);
default:
break;