Skip to content

Commit

Permalink
Fenced frames: allow CSP to check ancestors for frame-ancestors.
Browse files Browse the repository at this point in the history
To prevent information from flowing from an embedder into a fenced
frame, we have previously disabled checking ancestors of fenced frame
roots for the CSP frame-ancestors policy. There is now a need to allow
the frame-ancestors policy to look beyond the fenced frame root so that
embedders can control what is embedded in its page.

`window.fence.notifyEvent()` can be used to send information from a
fenced frame with unpartitioned data access to its embedder. Since 1 bit
is sent every click, a malicious embedder can exploit this and trick the
user into clicking a fenced frame in a certain way that leaks that
unpartitioned data.

The fenced frame can protect against this with the `frame-ancestors`
CSP, only allowing itself to be embedded in certain origins. For this to
work, the fenced frame needs to look beyond the fenced frame boundary
when calculating if it can load. Since this results in a data inflow
channel, this will only be allowed for fenced frames created from the
web platform or from Shared Storage, as those are the use cases where
data can flow into the fenced frame. Protected Audience-created fenced
frames will not have this capability, and will continue to not check
beyond the fenced frame root when calculating frame-ancestors.

This CL adds a new field to the fenced frame config/properties that
notes what API created the fenced frame. This is used in the
|AncestorThrottle| class to determine if/how to get the frame's direct
ancestor.

Change-Id: If7b335700319bad79ef3baf26a6d3f376ae22bc2
Bug: 341356673
  • Loading branch information
Liam Brady authored and chromium-wpt-export-bot committed May 23, 2024
1 parent 2d2b35a commit a8ac03d
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 4 deletions.
11 changes: 7 additions & 4 deletions fenced-frame/ancestor-throttle.https.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,24 @@

// Generate the url for the top level fenced frame, including the information
// needed to pass on to its nested iframe
const cross_origin = get_host_info().HTTPS_REMOTE_ORIGIN;
const origin = get_host_info().HTTPS_REMOTE_ORIGIN;
let fenced_frame_url = generateURL(
"resources/ancestor-throttle-inner.https.html",
[ancestor_key, embed_url, cross_origin_to_top_level_iframe]);
[ancestor_key, embed_url, cross_origin_to_top_level_iframe], true);
if (cross_origin_to_top_level_fenced_frame)
fenced_frame_url = getRemoteOriginURL(fenced_frame_url, true);

attachFencedFrame(fenced_frame_url);
const fenced_frame_config = await generateURNFromFledgeRawURL(
fenced_frame_url, [], true);

attachFencedFrame(fenced_frame_config);

// There is no API to observe whether the document in the FencedFrame loaded
// or not. Instead, set up a timeout. If the document loads, "loaded" will be
// sent to the server. Otherwise "blocked" will be sent after 3 seconds.
step_timeout(() => {
writeValueToServer(ancestor_key, "blocked");
}, 3000);
}, 1000);

// Get the result for the fenced frame's nested iframe.
const fenced_frame_result = await nextValueFromServer(ancestor_key);
Expand Down
43 changes: 43 additions & 0 deletions fenced-frame/csp-ancestors.https.sub.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<!DOCTYPE html>
<title>Test Content-Security-Policy frame-ancestors</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/utils.js"></script>
<script src="/common/dispatcher/dispatcher.js"></script>
<script src="resources/utils.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<body>
<script>
promise_test(async (t) => {
// The fenced frame loads with a frame-ancestors policy set. The only way for
// the fenced frame to know that it can't load is if it checks its parent's
// policy across the fenced frame boundary.
const frame = await attachFencedFrameContext({
headers: [[
'Content-Security-Policy',
'frame-ancestors {{hosts[alt][www1]}}:{{ports[https][0]}}'
]]
});
let timeout_promise =
new Promise(resolve => t.step_timeout(() => resolve('timeout'), 1000));
let execute_promise = frame.execute(() => {});
const result = await Promise.race([timeout_promise, execute_promise]);
assert_equals(result, 'timeout', 'The fenced frame should not load');
}, 'Fenced frames check beyond fenced boundary for CSP frame-ancestors');

promise_test(async () => {
// The Protected Audience fenced frame loads with a frame-ancestors policy
// set. It should be allowed to load even though the parent's origin isn't
// part of the policy.
const frame = await attachFencedFrameContext({
generator_api: 'fledge',
headers: [[
'Content-Security-Policy',
'frame-ancestors {{hosts[alt][www1]}}:{{ports[https][0]}}'
]]
});
await frame.execute(() => {});
}, 'Protected Audience fenced frames do not check beyond fenced boundary for ' +
'CSP frame-ancestors');
</script>
</body>

0 comments on commit a8ac03d

Please sign in to comment.