diff options
Diffstat (limited to '')
39 files changed, 1712 insertions, 0 deletions
diff --git a/testing/web-platform/tests/content-security-policy/inheritance/blob-inherits-from-meta-http-equiv-with-invalid-characters.html b/testing/web-platform/tests/content-security-policy/inheritance/blob-inherits-from-meta-http-equiv-with-invalid-characters.html new file mode 100644 index 0000000000..8463a2eaf1 --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/blob-inherits-from-meta-http-equiv-with-invalid-characters.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html> +<head> +<meta http-equiv="Content-Security-Policy" content=" + default-src 'none'; + script-src blob: 'nonce-abc'"> +<script nonce="abc" src="/resources/testharness.js"></script> +<script nonce="abc" src="/resources/testharnessreport.js"></script> +</head> +<script nonce="abc"> + async_test(t => { + var script = document.createElement("script"); + script.onerror = () => assert_unreached("FAIL should not have fired error event."); + script.onload = () => t.done(); + script.src = URL.createObjectURL(new Blob(["alert('PASS executed blob URL script.');"])); + document.head.appendChild(script); + }, "blob: URL inherits CSP from a meta tag whose contents have newline characters."); +</script> +</html> diff --git a/testing/web-platform/tests/content-security-policy/inheritance/blob-url-in-child-frame-self-navigate-inherits.sub.html b/testing/web-platform/tests/content-security-policy/inheritance/blob-url-in-child-frame-self-navigate-inherits.sub.html new file mode 100644 index 0000000000..f2b3d063e9 --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/blob-url-in-child-frame-self-navigate-inherits.sub.html @@ -0,0 +1,17 @@ +<!DOCTYPE html> +<html> + +<head> + <script nonce="abc" src="/resources/testharness.js"></script> + <script nonce="abc" src="/resources/testharnessreport.js"></script> +</head> + +<!-- This tests that navigating a main window to a local scheme preserves the current CSP. + We need to test this in a main window with no parent/opener so we use + a link with target=_blank and rel=noopener. --> +<body> + <iframe src="support/navigate-self-to-blob.html?csp=script-src%20%27nonce-abc%27&report_id={{$id:uuid()}}"></iframe> + <script async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=script-src%20%27nonce-abc%27&reportID={{$id}}'></script> +</body> + +</html> diff --git a/testing/web-platform/tests/content-security-policy/inheritance/blob-url-in-main-window-self-navigate-inherits.sub.html b/testing/web-platform/tests/content-security-policy/inheritance/blob-url-in-main-window-self-navigate-inherits.sub.html new file mode 100644 index 0000000000..3b54528d56 --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/blob-url-in-main-window-self-navigate-inherits.sub.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<html> + +<head> + <script nonce="abc" src="/resources/testharness.js"></script> + <script nonce="abc" src="/resources/testharnessreport.js"></script> +</head> + +<!-- This tests that navigating a main window to a local scheme preserves the current CSP. + We need to test this in a main window with no parent/opener so we use + a link with target=_blank and rel=noopener. --> +<body> + <script> + const a = document.createElement("a") + a.href = "support/navigate-self-to-blob.html?csp=script-src%20%27nonce-abc%27&report_id={{$id:uuid()}}"; + a.target = "_blank" + a.rel = "noopener" + a.click() + </script> + <script async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=script-src%20%27nonce-abc%27&reportID={{$id}}'></script> +</body> + +</html> diff --git a/testing/web-platform/tests/content-security-policy/inheritance/blob-url-inherits-from-initiator.sub.html b/testing/web-platform/tests/content-security-policy/inheritance/blob-url-inherits-from-initiator.sub.html new file mode 100644 index 0000000000..72d59325d1 --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/blob-url-inherits-from-initiator.sub.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Blob URL inherits CSP from initiator.</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + let testCases = [ + { + initiator_origin: window.origin, + name: "Initiator is same-origin with target frame.", + }, + { + initiator_origin: "http://{{hosts[alt][]}}:{{ports[http][0]}}", + name: "Initiator is cross-origin with target frame.", + }, + ]; + + testCases.forEach(test => { + async_test(t => { + // Create a popup. At the beginning, the popup has no CSPs. + let target = window.open(); + t.add_cleanup(() => target.close()); + + // Create a child frame in the popup. The child frame has + // Content-Security-Policy: script-src 'unsafe-inline'. The child frame + // will navigate the popup to a blob URL, which will try if eval is + // allowed and message back. + let initiator = target.document.createElement('iframe'); + initiator.sandbox = "allow-scripts allow-same-origin allow-top-navigation"; + initiator.src = test.initiator_origin + + "/content-security-policy/inheritance/support/navigate-parent-to-blob.html"; + + window.addEventListener("message", t.step_func(e => { + if (e.source !== target) return; + assert_equals(e.data, "eval blocked", + "Eval should be blocked by CSP in blob URL."); + t.done(); + })); + + target.document.body.appendChild(initiator); + }, test.name); + }); +</script> diff --git a/testing/web-platform/tests/content-security-policy/inheritance/document-write-iframe.html b/testing/web-platform/tests/content-security-policy/inheritance/document-write-iframe.html new file mode 100644 index 0000000000..d6ad88ddc9 --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/document-write-iframe.html @@ -0,0 +1,65 @@ +<!DOCTYPE html> +<head> + <meta http-equiv="Content-Security-Policy" content="img-src 'none'"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <title>document.open() does not change Content Security Policies</title> +</head> +<body> + <script> + let message_from = (w) => { + return new Promise(resolve => { + let listener = msg => { + if (msg.source != w) + return; + window.removeEventListener('message', listener); + resolve(msg.data); + }; + window.addEventListener('message', listener); + }); + }; + + var documentBody = function(should_load) { + let image = should_load ? "pass.png" : "fail.png"; + return ` + <script> + function loaded() { + window.top.postMessage("loaded", '*'); + }; + window.addEventListener('securitypolicyviolation', function(e) { + window.top.postMessage("blocked", '*'); + }); + </scr`+`ipt> + <img src='/content-security-policy/support/${image}' onload='loaded()'>`; + }; + + promise_test(async () => { + let iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + + let msg = message_from(iframe.contentWindow); + let doc = iframe.contentWindow.document; + doc.open(); + doc.write("<html><body>" + documentBody(false) + "</body></html>"); + doc.close(); + assert_equals(await msg, "blocked"); + }, "document.open() keeps inherited CSPs on empty iframe."); + + promise_test(async () => { + let iframe = document.createElement('iframe'); + let loaded = new Promise(resolve => iframe.onload = resolve); + iframe.src = "/common/blank.html"; + document.body.appendChild(iframe); + await loaded; + + let msg = message_from(iframe.contentWindow); + let doc = iframe.contentWindow.document; + doc.open(); + doc.write("<html><body>" + documentBody(true) + "</body></html>"); + doc.close(); + assert_equals(await msg, "loaded"); + }, "document.open() does not change delivered CSPs."); + + </script> +</body> +</html> diff --git a/testing/web-platform/tests/content-security-policy/inheritance/frame-src-javascript-url.html b/testing/web-platform/tests/content-security-policy/inheritance/frame-src-javascript-url.html new file mode 100644 index 0000000000..b08da85e87 --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/frame-src-javascript-url.html @@ -0,0 +1,40 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<meta http-equiv="Content-Security-Policy" content="frame-src 'none'"> + +<script> + const iframe_url = new URL("./support/empty.html", location.href); + + // Regression test for: https://crbug.com/1064676 + promise_test(async (t) => { + await new Promise(r => window.onload = r); + + let url = `javascript: + + window.addEventListener('securitypolicyviolation', e => { + parent.postMessage({ + originalPolicy: e.originalPolicy, + blockedURI: e.blockedURI, + }); + }); + + let iframe = document.createElement('iframe'); + iframe.src = '${iframe_url}'; + document.body.appendChild(iframe); + + `; + + let iframe = document.createElement('iframe'); + iframe.src = encodeURI(url.replace(/\n/g, "")); + + let violation = new Promise(r => window.addEventListener("message", r)); + document.body.appendChild(iframe); + let {data} = await violation; + + assert_equals(data.originalPolicy, "frame-src 'none'"); + assert_equals(data.blockedURI, iframe_url.toString()); + + }, "<iframe src='javascript:...'>'s inherits policy (dynamically inserted <iframe> is blocked)"); + +</script> diff --git a/testing/web-platform/tests/content-security-policy/inheritance/history-iframe.sub.html b/testing/web-platform/tests/content-security-policy/inheritance/history-iframe.sub.html new file mode 100644 index 0000000000..412b3ac346 --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/history-iframe.sub.html @@ -0,0 +1,178 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> + +<meta http-equiv="Content-Security-Policy" content="img-src 'none'"> +<body> +<script> + let message_from = (source_token, starts_with) => { + return new Promise(resolve => { + window.addEventListener('message', msg => { + if (msg.data.token === source_token) { + if (!starts_with || msg.data.msg.startsWith(starts_with)) + resolve(msg.data.msg); + } + }); + }); + }; + + const img_url = window.origin + "/content-security-policy/support/fail.png"; + + const img_tag_string = img_token => ` + <img src="${img_url}" + onload="top.postMessage( + {msg: 'img loaded', token: '${img_token}'}, '*');" + onerror="top.postMessage( + {msg: 'img blocked', token: '${img_token}'}, '*');" + > + `; + + const html_test_payload = img_token => ` + <!doctype html> + <script> + function add_image() { + let img = document.createElement('img'); + img.onload = () => top.postMessage( + {msg: 'img loaded', token: '${img_token}'}, '*'); + img.onerror = () => top.postMessage( + {msg: 'img blocked', token: '${img_token}'}, '*'); + img.src = '${img_url}'; + document.body.appendChild(img); + } + </scr`+`ipt> + <body onpageshow="add_image();"></body> + `; + let blob_url = blob_token => URL.createObjectURL( + new Blob([html_test_payload(blob_token)], { type: 'text/html' })); + + let write_img_to_about_blank = async (t, iframe, img_token) => { + await t.step_wait( + condition = () => { + try { + return iframe.contentWindow.location.href == "about:blank"; + } catch {} + return false; + }, + description = "Wait for the iframe to navigate.", + timeout=6000, + interval=50); + + let div = iframe.contentDocument.createElement('div'); + div.innerHTML = img_tag_string(img_token); + iframe.contentDocument.body.appendChild(div); + }; + + let testCases = [ + test_token => ({ + token: test_token, + url: "about:blank", + add_img_function: (t, iframe) => + write_img_to_about_blank(t, iframe, test_token), + other_origin: window.origin, + name: '"about:blank" document is navigated back from history same-origin.', + }), + test_token => ({ + token: test_token, + url: "about:blank", + add_img_function: (t, iframe) => + write_img_to_about_blank(t, iframe, test_token), + other_origin: "http://{{hosts[alt][]}}:{{ports[http][0]}}", + name: '"about:blank" document is navigated back from history cross-origin.', + }), + test_token => ({ + token: test_token, + url: blob_url(test_token), + other_origin: window.origin, + name: 'blob URL document is navigated back from history same-origin.', + }), + test_token => ({ + token: test_token, + url: blob_url(test_token), + other_origin: "http://{{hosts[alt][]}}:{{ports[http][0]}}", + name: 'blob URL document is navigated back from history cross-origin.', + }), + test_token => ({ + token: test_token, + url: `data:text/html,${html_test_payload(test_token)}`, + other_origin: window.origin, + name: 'data URL document is navigated back from history same-origin.', + }), + test_token => ({ + token: test_token, + url: `data:text/html,${html_test_payload(test_token)}`, + other_origin: "http://{{hosts[alt][]}}:{{ports[http][0]}}", + name: 'data URL document is navigated back from history cross-origin.', + }), + test_token => ({ + token: test_token, + srcdoc: `${html_test_payload(test_token)}`, + other_origin: window.origin, + name: 'srcdoc iframe is navigated back from history same-origin.', + }), + test_token => ({ + token: test_token, + srcdoc: `${html_test_payload(test_token)}`, + other_origin: "http://{{hosts[alt][]}}:{{ports[http][0]}}", + name: 'srcdoc iframe is navigated back from history cross-origin.', + }), + ].map(f => f(token())); + + testCases.forEach(testCase => { + promise_test(async t => { + // Create an iframe. + let iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + + // Perform a real navigation in the iframe. This is needed because the + // initial empty document is not stored in history (so there is no way of + // navigating back to it and test history inheritance). + const token_1 = token(); + let loaded_1 = message_from(token_1); + iframe.contentWindow.location = testCase.other_origin + + "/content-security-policy/inheritance/support" + + `/postmessage-top.html?token=${token_1}`; + assert_equals(await loaded_1, "ready", + "Could not navigate iframe."); + + // Navigate to the local scheme document. + let message = message_from(testCase.token); + if (testCase.url) + iframe.contentWindow.location = testCase.url; + else + iframe.srcdoc = testCase.srcdoc; + + // If the local scheme document is "about:blank", we need to write its + // content now. + if (testCase.add_img_function) { + testCase.add_img_function(t, iframe); + } + + // Check that the local scheme document inherits CSP from the initiator. + assert_equals(await message, "img blocked", + "Image should be blocked by CSP inherited from navigation initiator."); + + // Navigate to another page, which will navigate back. + const token_2 = token(); + let loaded_2 = message_from(token_2, "ready"); + let message_2 = message_from(testCase.token, "img"); + iframe.contentWindow.location = testCase.other_origin + + "/content-security-policy/inheritance/support" + + `/message-top-and-navigate-back.html?token=${token_2}`; + assert_equals(await loaded_2, "ready", + "Could not navigate iframe."); + + // If the local scheme document is "about:blank", we need to write its + // content again. + if (testCase.add_img_function) { + testCase.add_img_function(t, iframe); + } + + // Check that the local scheme document reloaded from history still has + // the original CSPs. + assert_equals(await message_2, "img blocked", + "Image should be blocked by CSP reloaded from history."); + }, "History navigation in iframe: " + testCase.name); + }); +</script> +</body> diff --git a/testing/web-platform/tests/content-security-policy/inheritance/history.sub.html b/testing/web-platform/tests/content-security-policy/inheritance/history.sub.html new file mode 100644 index 0000000000..5ea6abe8fb --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/history.sub.html @@ -0,0 +1,195 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> + +<meta http-equiv="Content-Security-Policy" content="img-src 'none'"> + +<script> + let message_from = (source_token, starts_with) => { + return new Promise(resolve => { + window.addEventListener('message', msg => { + if (msg.data.token === source_token) { + if (!starts_with || msg.data.msg.startsWith(starts_with)) + resolve(msg.data.msg); + } + }); + }); + }; + + const img_url = window.origin + "/content-security-policy/support/fail.png"; + + const function_addImage_string = img_token => ` + function addImage() { + let img = document.createElement('img'); + img.src = '${img_url}'; + img.onload = () => opener.postMessage( + {msg: 'img loaded', token: '${img_token}'}, '*'); + img.onerror = () => opener.postMessage( + {msg: 'img blocked', token: '${img_token}'}, '*'); + document.body.appendChild(img); + } + `; + + const img_tag_string = img_token => ` + <img src="${img_url}" + onload="opener.postMessage( + {msg: 'img loaded', token: '${img_token}'}, '*');" + onerror="opener.postMessage( + {msg: 'img blocked', token: '${img_token}'}, '*');" + > + `; + + let write_img_to_popup = (popup, img_token) => { + let div = popup.document.createElement('div'); + div.innerHTML = img_tag_string(img_token); + popup.document.body.appendChild(div); + }; + + // A beforeunload event listener disables bfcache (Firefox only). + // + // Note: Chrome enables bfcache only on HTTP/HTTPS documents, so a blob will + // never be put in the bfcache. Moreover with Chrome, bfcache needs a single + // top-level browsing context in the browsing context group. Since we are + // using window.open() below, the back-forward cache is not triggered. + const disable_bfcache = ` + window.addEventListener('beforeunload', function(event) { + eval('1+1'); + }); + `; + + const blob_payload = blob_token => ` + <!doctype html> + <script>window.window_token = "${blob_token}";</scr`+`ipt> + <script>${function_addImage_string(`${blob_token}`)}</scr`+`ipt> + <body onpageshow="addImage();"></body> + `; + let blob_url = blob_token => URL.createObjectURL( + new Blob([blob_payload(blob_token)], { type: 'text/html' })); + + const blob_payload_no_bfcache = blob_token => ` + <!doctype html> + <script>window.window_token = "${blob_token}";</scr`+`ipt> + <script>${disable_bfcache}</scr`+`ipt> + <script>${function_addImage_string(`${blob_token}`)}</scr`+`ipt> + <body onpageshow="addImage();"></body> + `; + let blob_url_no_bfcache = blob_token => URL.createObjectURL( + new Blob([blob_payload_no_bfcache(blob_token)], { type: 'text/html' })); + + let testCases = [ + test_token => ({ + token: test_token, + url: "about:blank", + add_img_function: popup => write_img_to_popup(popup, test_token), + other_origin: window.origin, + name: '"about:blank" document is navigated back from history same-origin.', + }), + test_token => ({ + token: test_token, + url: "about:blank", + add_img_function: popup => write_img_to_popup(popup, test_token), + other_origin: "http://{{hosts[alt][]}}:{{ports[http][0]}}", + name: '"about:blank" document is navigated back from history cross-origin.', + }), + test_token => ({ + token: test_token, + url: blob_url(test_token), + other_origin: window.origin, + name: 'blob URL document is navigated back from history same-origin.', + }), + test_token => ({ + token: test_token, + url: blob_url(test_token), + other_origin: "http://{{hosts[alt][]}}:{{ports[http][0]}}", + name: 'blob URL document is navigated back from history cross-origin.', + }), + test_token => ({ + token: test_token, + url: blob_url_no_bfcache(test_token), + other_origin: window.origin, + name: 'blob URL document is navigated back from history (without bfcache on Firefox) same-origin.', + }), + test_token => ({ + token: test_token, + url: blob_url_no_bfcache(test_token), + other_origin: "http://{{hosts[alt][]}}:{{ports[http][0]}}", + name: 'blob URL document is navigated back from history (without bfcache on Firefox) cross-origin.', + }), + ].map(f => f(token())); + + let async_promise_test = (promise, description) => { + async_test(test => { + promise(test) + .then(() => {test.done();}) + .catch(test.step_func(error => { throw error; })); + }, description); + }; + + testCases.forEach(testCase => { + async_promise_test(async t => { + // Create a popup. + let popup = window.open(); + + // Closing fails sometimes on Firefox: + // https://bugzilla.mozilla.org/show_bug.cgi?id=1698093 + t.add_cleanup(() => { popup.close(); }); + + // Perform a real navigation in the popup. This is needed because the + // initial empty document is not stored in history (so there is no way of + // navigating back to it and test history inheritance). + const token_1 = token(); + let loaded_1 = message_from(token_1); + popup.location = testCase.other_origin + + `/content-security-policy/inheritance/support` + + `/postmessage-opener.html?token=${token_1}`; + assert_equals(await loaded_1, "ready", + "Could not open and navigate popup."); + + // Navigate to the local scheme document. We need to wait for the + // navigation to succeed. + let wait = () => t.step_wait( + condition = () => { + try { + return popup.location.href == testCase.url; + } catch {} + return false; + }, + description = "Wait for the popup to navigate.", + timeout=3000, + interval=50); + + let message = message_from(testCase.token); + popup.location = testCase.url; + await wait(); + if (testCase.add_img_function) { + testCase.add_img_function(popup); + } + + // Check that the local scheme document inherits CSP from the initiator. + assert_equals(await message, "img blocked", + "Image should be blocked by CSP inherited from navigation initiator."); + + const token_2 = token(); + let loaded_2 = message_from(token_2, "ready"); + let message_2 = message_from(testCase.token, "img"); + // Navigate to another page, which will navigate back. + popup.location = testCase.other_origin + + `/content-security-policy/inheritance/support` + + `/message-opener-and-navigate-back.html?token=${token_2}`; + assert_equals(await loaded_2, "ready", + "Could not navigate popup."); + + // We need to wait for the history navigation to be performed. + await wait(); + + // Check that the "about:blank" document reloaded from history has the + // original CSPs. + if (testCase.add_img_function) { + testCase.add_img_function(popup); + } + assert_equals(await message_2, "img blocked", + "Image should be blocked by CSP reloaded from history."); + }, "History navigation: " + testCase.name); + }); +</script> diff --git a/testing/web-platform/tests/content-security-policy/inheritance/iframe-all-local-schemes-inherit-self.sub.html b/testing/web-platform/tests/content-security-policy/inheritance/iframe-all-local-schemes-inherit-self.sub.html new file mode 100644 index 0000000000..73e974e51a --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/iframe-all-local-schemes-inherit-self.sub.html @@ -0,0 +1,102 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<meta http-equiv="Content-Security-Policy" content="img-src 'self'"> + +<body> + +<script> + function wait_for_error_from_frame(frame, test) { + window.addEventListener('message', test.step_func(e => { + if (e.source != frame.contentWindow) + return; + assert_equals(e.data, "load"); + frame.remove(); + test.done(); + })); + } + + async_test(t => { + var i = document.createElement('iframe'); + document.body.appendChild(i); + + var img = document.createElement('img'); + img.onload = t.step_func_done(_ => i.remove()); + img.onerror = t.unreached_func(); + i.contentDocument.body.appendChild(img); + img.src = "{{location[server]}}/images/red-16x16.png"; + }, "<iframe>'s about:blank inherits policy."); + + async_test(t => { + var i = document.createElement('iframe'); + i.srcdoc = ` + <img src='{{location[server]}}/images/red-16x16.png' + onload='window.top.postMessage("load", "*");' + onerror='window.top.postMessage("error", "*");' + > + `; + + wait_for_error_from_frame(i, t); + + document.body.appendChild(i); + }, "<iframe srcdoc>'s inherits policy."); + + async_test(t => { + var i = document.createElement('iframe'); + var b = new Blob( + [` + <img src='{{location[server]}}/images/red-16x16.png' + onload='window.top.postMessage("load", "*");' + onerror='window.top.postMessage("error", "*");' + > + `], {type:"text/html"}); + i.src = URL.createObjectURL(b); + + wait_for_error_from_frame(i, t); + + document.body.appendChild(i); + }, "<iframe src='blob:...'>'s inherits policy."); + + async_test(t => { + var i = document.createElement('iframe'); + i.src = `data:text/html,<img src='{{location[server]}}/images/red-16x16.png' + onload='window.top.postMessage("load", "*");' + onerror='window.top.postMessage("error", "*");' + >`; + + wait_for_error_from_frame(i, t); + + document.body.appendChild(i); + }, "<iframe src='data:...'>'s inherits policy."); + + async_test(t => { + var i = document.createElement('iframe'); + i.src = `javascript:"<img src='{{location[server]}}/images/red-16x16.png' + onload='window.top.postMessage(\\"load\\", \\"*\\");' + onerror='window.top.postMessage(\\"error\\", \\"*\\");' + >"`; + + wait_for_error_from_frame(i, t); + + document.body.appendChild(i); + }, "<iframe src='javascript:...'>'s inherits policy."); + + async_test(t => { + var i = document.createElement('iframe'); + var b = new Blob( + [` + <img src='{{location[server]}}/images/red-16x16.png' + onload='window.top.postMessage("load", "*");' + onerror='window.top.postMessage("error", "*");' + > + `], {type:"text/html"}); + i.src = URL.createObjectURL(b); + i.sandbox = 'allow-scripts'; + + wait_for_error_from_frame(i, t); + + document.body.appendChild(i); + }, "<iframe sandbox src='blob:...'>'s inherits policy. (opaque origin sandbox)"); + +</script> diff --git a/testing/web-platform/tests/content-security-policy/inheritance/iframe-all-local-schemes.sub.html b/testing/web-platform/tests/content-security-policy/inheritance/iframe-all-local-schemes.sub.html new file mode 100644 index 0000000000..4b787e0c18 --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/iframe-all-local-schemes.sub.html @@ -0,0 +1,180 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<meta http-equiv="Content-Security-Policy" content="img-src 'none'"> + +<body> + +<script> + function wait_for_error_from_frame(frame, test) { + window.addEventListener('message', test.step_func(e => { + if (e.source != frame.contentWindow) + return; + assert_equals(e.data, "error"); + frame.remove(); + test.done(); + })); + } + + function wait_for_error_from_window(opened_window, test) { + window.addEventListener('message', test.step_func(e => { + if (e.source != opened_window) + return; + assert_equals(e.data, "error"); + opened_window.close(); + test.done(); + })); + } + + async_test(t => { + var i = document.createElement('iframe'); + document.body.appendChild(i); + + var img = document.createElement('img'); + img.onerror = t.step_func_done(_ => i.remove()); + img.onload = t.unreached_func(); + i.contentDocument.body.appendChild(img); + img.src = "{{location[server]}}/images/red-16x16.png"; + }, "<iframe>'s about:blank inherits policy."); + + async_test(t => { + var w = window.open("about:blank"); + + let then = t.step_func(() => { + then = () => {}; + var img = w.document.createElement('img'); + img.onerror = t.step_func_done(_ => w.close()); + img.onload = t.unreached_func(); + w.document.body.appendChild(img); + img.src = "{{location[server]}}/images/red-16x16.png"; + }); + + // There are now interoperable way to wait for the initial about:blank + // document to load. Chrome loads it synchronously, hence we can't wait for + // w.onload. On the other side Firefox loads the initial empty document + // later and we can wait for the onload event. + w.onload = then; + setTimeout(then, 200); + + // Navigations to about:blank happens synchronously. There is no need to + // wait for the document to load. + }, "window about:blank inherits policy."); + + async_test(t => { + var i = document.createElement('iframe'); + i.srcdoc = ` + <img src='{{location[server]}}/images/red-16x16.png' + onload='window.top.postMessage("load", "*");' + onerror='window.top.postMessage("error", "*");' + > + `; + + wait_for_error_from_frame(i, t); + + document.body.appendChild(i); + }, "<iframe srcdoc>'s inherits policy."); + + async_test(t => { + var i = document.createElement('iframe'); + var b = new Blob( + [` + <img src='{{location[server]}}/images/red-16x16.png' + onload='window.top.postMessage("load", "*");' + onerror='window.top.postMessage("error", "*");' + > + `], {type:"text/html"}); + i.src = URL.createObjectURL(b); + + wait_for_error_from_frame(i, t); + + document.body.appendChild(i); + }, "<iframe src='blob:...'>'s inherits policy."); + + async_test(t => { + var b = new Blob( + [` + <img src='{{location[server]}}/images/red-16x16.png' + onload='window.opener.postMessage("load", "*");' + onerror='window.opener.postMessage("error", "*");' + > + `], {type:"text/html"}); + let url = URL.createObjectURL(b); + var w = window.open(url); + wait_for_error_from_window(w, t); + }, "window url='blob:...' inherits policy."); + + async_test(t => { + var i = document.createElement('iframe'); + i.src = `data:text/html,<img src='{{location[server]}}/images/red-16x16.png' + onload='window.top.postMessage("load", "*");' + onerror='window.top.postMessage("error", "*");' + >`; + + wait_for_error_from_frame(i, t); + + document.body.appendChild(i); + }, "<iframe src='data:...'>'s inherits policy."); + + // Opening a window toward a data-url isn't allowed anymore. Hence, it can't + // be tested. + + async_test(t => { + var i = document.createElement('iframe'); + i.src = `javascript:"<img src='{{location[server]}}/images/red-16x16.png' + onload='window.top.postMessage(\\"load\\", \\"*\\");' + onerror='window.top.postMessage(\\"error\\", \\"*\\");' + >"`; + + wait_for_error_from_frame(i, t); + + document.body.appendChild(i); + }, "<iframe src='javascript:...'>'s inherits policy (static <img> is blocked)"); + + async_test(t => { + let url = `javascript:"<img src='{{location[server]}}/images/red-16x16.png' + onload='window.opener.postMessage(\\"load\\", \\"*\\");' + onerror='window.opener.postMessage(\\"error\\", \\"*\\");' + >"`; + + let w = window.open(url); + wait_for_error_from_window(w, t); + }, "window url='javascript:...'>'s inherits policy (static <img> is blocked)"); + + // Same as the previous javascript-URL test, but instead of loading the <img> + // from the new document, this one is created from the initial empty document, + // while evaluating the javascript-url. + // See https://crbug.com/1064676 + async_test(t => { + let url = `javascript: + let img = document.createElement('img'); + img.onload = () => window.top.postMessage('load', '*'); + img.onerror = () => window.top.postMessage('error', '*'); + img.src = '{{location[server]}}/images/red-16x16.png'; + document.body.appendChild(img); + `; + var i = document.createElement('iframe'); + i.src = encodeURI(url.replace(/\n/g, "")); + wait_for_error_from_frame(i, t); + + document.body.appendChild(i); + }, "<iframe src='javascript:...'>'s inherits policy (dynamically inserted <img> is blocked)"); + + async_test(t => { + var i = document.createElement('iframe'); + var b = new Blob( + [` + <img src='{{location[server]}}/images/red-16x16.png' + onload='window.top.postMessage("load", "*");' + onerror='window.top.postMessage("error", "*");' + > + `], {type:"text/html"}); + i.src = URL.createObjectURL(b); + i.sandbox = 'allow-scripts'; + + wait_for_error_from_frame(i, t); + + document.body.appendChild(i); + }, "<iframe sandbox src='blob:...'>'s inherits policy. (opaque origin sandbox)"); + +</script> diff --git a/testing/web-platform/tests/content-security-policy/inheritance/iframe-srcdoc-history-inheritance.html b/testing/web-platform/tests/content-security-policy/inheritance/iframe-srcdoc-history-inheritance.html new file mode 100644 index 0000000000..907c88e813 --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/iframe-srcdoc-history-inheritance.html @@ -0,0 +1,63 @@ +<!DOCTYPE html> +<head> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> +</head> + +<body> +<iframe></iframe> +<script> +promise_test(async t => { + // Wait for the page to load + one task so that navigations from here on are + // not done in "replace" mode. + await new Promise(resolve => window.onload = () => t.step_timeout(resolve, 0)); + const iframe = document.querySelector('iframe'); + + iframe.srcdoc = ` + <h1>This is a dummy page that should not store the inherited policy + container in this history entry</h1> + `; + + await new Promise(resolve => iframe.onload = () => t.step_timeout(resolve, 0)); + + // Navigate the iframe away. + iframe.contentWindow.location.href = "/common/blank.html"; + await new Promise(resolve => iframe.onload = resolve); + + // Tighten the outer page's security policy. + const meta = document.createElement("meta"); + meta.setAttribute("http-equiv", "Content-Security-Policy"); + meta.setAttribute("content", "img-src 'none'"); + document.head.append(meta); + + // Navigate the iframe back to the `about:srcdoc` page (this should work + // independent of whether the implementation stores the srcdoc contents in the + // history entry or reclaims it from the attribute). + iframe.contentWindow.history.back(); + await new Promise(resolve => iframe.onload = resolve); + + const img = iframe.contentDocument.createElement('img'); + + const promise = new Promise((resolve, reject) => { + img.onload = resolve; + // If the img is blocked because of Content Security Policy, a violation + // should be reported first, and the test will fail. If for some other + // reason the error event is fired without the violation being reported, + // something else went wrong, hence the test should fail. + img.error = e => { + reject(new Error("The srcdoc iframe's img failed to load but not due to " + + "a CSP violation")); + }; + iframe.contentDocument.onsecuritypolicyviolation = e => { + reject(new Error("The srcdoc iframe's img has been blocked by the " + + "new CSP. It means it was different and wasn't restored from history")); + }; + }); + // The srcdoc iframe tries to load an image, which should succeed. + img.src = "/common/square.png"; + + return promise; +}); +</script> +</body> +</html> diff --git a/testing/web-platform/tests/content-security-policy/inheritance/iframe-srcdoc-inheritance.html b/testing/web-platform/tests/content-security-policy/inheritance/iframe-srcdoc-inheritance.html new file mode 100644 index 0000000000..e05150762f --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/iframe-srcdoc-inheritance.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<head> + <meta http-equiv="Content-Security-Policy" content="img-src 'self'"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> +</head> +<body> + <script> + var t1 = async_test("First image should be blocked"); + var t2 = async_test("Second image should be blocked"); + window.onmessage = t1.step_func_done(function(e) { + if (e.data == "img blocked") { + frames[0].frames[0].frameElement.srcdoc = + `<script> + window.addEventListener('securitypolicyviolation', function(e) { + if (e.violatedDirective == 'img-src') { + top.postMessage('img blocked', '*'); + } + }) + </scr` + `ipt> + <img src='/content-security-policy/support/fail.png' + onload='top.postMessage("img loaded", "*")'/>`; + window.onmessage = t2.step_func_done(function(e) { + if (e.data != "img blocked") + assert_true(false, "The second image should have been blocked"); + }); + } else { + assert_true(false, "The first image should have been blocked"); + } + }); + </script> + <iframe src="support/srcdoc-child-frame.html"></iframe> +</body> +</html> diff --git a/testing/web-platform/tests/content-security-policy/inheritance/inheritance-from-initiator.sub.html b/testing/web-platform/tests/content-security-policy/inheritance/inheritance-from-initiator.sub.html new file mode 100644 index 0000000000..4621c57d45 --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/inheritance-from-initiator.sub.html @@ -0,0 +1,173 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<meta http-equiv="Content-Security-Policy" content="img-src http://{{hosts[][www]}}:{{ports[http][0]}}"> +<body> + <script> + let message_from = w => { + return new Promise(resolve => { + window.addEventListener('message', msg => { + if (msg.source == w) { + resolve(msg.data); + } + }); + }); + }; + + // `iframe_a` and `iframe_b` are two helper iframes with different + // CSPs. + let iframe_a, iframe_b; + + // Setup `iframe_a` and `iframe_b`. + promise_setup(async () => { + iframe_a = document.createElement('iframe'); + iframe_a.src = "./support/iframe-do.sub.html?" + + "img-src=http://{{hosts[][www1]}}:{{ports[http][0]}}"; + document.body.appendChild(iframe_a); + await message_from(iframe_a.contentWindow); + + iframe_b = document.createElement('iframe'); + iframe_b.id = 'iframe_b'; + iframe_b.src = "./support/iframe-do.sub.html?" + + "img-src=http://{{hosts[][www2]}}:{{ports[http][0]}}"; + document.body.appendChild(iframe_b); + await message_from(iframe_b.contentWindow); + }); + + let test_iframe_id_counter = 0; + + // Helper function to create the target iframe of a navigation. + let create_test_iframe = async () => { + let test_iframe = document.createElement('iframe'); + test_iframe.id = "test_iframe_" + test_iframe_id_counter++; + test_iframe.name = test_iframe.id; + document.body.appendChild(test_iframe); + return test_iframe; + } + + // The following code will try loading several images and check + // whether CSP has been inherited by the parent ("p"), `iframe_a` + // ("a") or `iframe_b` ("b"). It will post a message to the top + // with the result. + let data_payload = ` + <body><script> + new Promise(async (resolve, reject) => { + const img_path = "/content-security-policy/support/pass.png"; + + let img_loaded = (origin) => new Promise(resolve => { + let img = document.createElement('img'); + img.onerror = () => resolve(false); + img.onload = () => resolve(true); + img.src = origin + img_path; + document.body.appendChild(img); + }); + + inherited_from_p = await img_loaded( + "http://{{hosts[][www]}}:{{ports[http][0]}}"); + inherited_from_a = await img_loaded( + "http://{{hosts[][www1]}}:{{ports[http][0]}}"); + inherited_from_b = await img_loaded( + "http://{{hosts[][www2]}}:{{ports[http][0]}}"); + + if (inherited_from_a + inherited_from_b + + inherited_from_p !== 1) { + reject("Exactly one CSP should be inherited"); + } + if (inherited_from_a) resolve("a"); + if (inherited_from_b) resolve("b"); + if (inherited_from_p) resolve("p"); + }).then(from => top.postMessage(from, '*'), + error => top.postMessage(error, '*')); + </scr`+`ipt></body> + `; + + let data_url = "data:text/html;base64," + btoa(data_payload); + + promise_test(async t => { + let test_iframe = await create_test_iframe(); + iframe_a.contentWindow.postMessage( + `parent.document.getElementById('${test_iframe.id}').src = '${data_url}'`); + + assert_equals(await message_from(test_iframe.contentWindow), "p"); + }, "Setting src inherits from parent."); + + promise_test(async t => { + let test_iframe = await create_test_iframe(); + iframe_a.contentWindow.postMessage( + `parent.document.getElementById('${test_iframe.id}').contentWindow.location = '${data_url}'`); + + assert_equals(await message_from(test_iframe.contentWindow), "a"); + }, "Changing contentWindow.location inherits from who changed it."); + + promise_test(async t => { + let test_iframe = await create_test_iframe(); + window.navigate_test_iframe = () => { + test_iframe.contentWindow.location = data_url; + }; + iframe_a.contentWindow.postMessage(`parent.navigate_test_iframe();`); + assert_equals(await message_from(test_iframe.contentWindow), "p"); + }, "Changing contentWindow.location indirectly inherits from who changed it directly."); + + promise_test(async t => { + let test_iframe = await create_test_iframe(); + iframe_a.contentWindow.postMessage( + `window.open('${data_url}', "${test_iframe.name}")`); + + assert_equals(await message_from(test_iframe.contentWindow), "a"); + }, "window.open() inherits from caller."); + + promise_test(async t => { + let test_iframe = await create_test_iframe(); + let a = iframe_b.contentDocument.createElement('a'); + a.id = 'a'; + a.href = data_url; + a.target = test_iframe.name; + iframe_b.contentDocument.body.appendChild(a); + + iframe_a.contentWindow.postMessage( + `parent.document.getElementById('iframe_b').contentDocument.getElementById('a').click();`); + + assert_equals(await message_from(test_iframe.contentWindow), "b"); + iframe_b.contentDocument.body.removeChild(a); + }, "Click on anchor inherits from owner of the anchor."); + + promise_test(async t => { + let test_iframe = await create_test_iframe(); + let form = iframe_b.contentDocument.createElement('form'); + form.id = 'form'; + form.action = data_url; + form.target = test_iframe.name; + form.method = "POST"; + iframe_b.contentDocument.body.appendChild(form); + + iframe_a.contentWindow.postMessage( + `parent.document.getElementById('iframe_b').contentDocument.getElementById('form').submit();`); + + assert_equals(await message_from(test_iframe.contentWindow), "b"); + iframe_b.contentDocument.body.removeChild(form); + }, "Form submission through submit() inherits from owner of form."); + + promise_test(async t => { + let test_iframe = await create_test_iframe(); + let form = iframe_b.contentDocument.createElement('form'); + form.id = 'form'; + form.action = data_url; + form.target = test_iframe.name; + form.method = "POST"; + iframe_b.contentDocument.body.appendChild(form); + let button = iframe_b.contentDocument.createElement('button'); + button.type = "submit"; + button.value = "submit"; + button.id = "button"; + form.appendChild(button); + + iframe_a.contentWindow.postMessage( + `parent.document.getElementById('iframe_b').contentDocument.getElementById('button').click();`); + + assert_equals(await message_from(test_iframe.contentWindow), "b"); + iframe_b.contentDocument.body.removeChild(form); + }, "Form submission through button click inherits from owner of form."); + + </script> +</body> diff --git a/testing/web-platform/tests/content-security-policy/inheritance/inherited-csp-list-modifications-are-local.html b/testing/web-platform/tests/content-security-policy/inheritance/inherited-csp-list-modifications-are-local.html new file mode 100644 index 0000000000..c473b3f426 --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/inherited-csp-list-modifications-are-local.html @@ -0,0 +1,49 @@ +<!DOCTYPE html> +<head> + <meta http-equiv="Content-Security-Policy" content="script-src 'unsafe-inline' 'self'"> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <!-- Tests that mutations inside a context that inherits a copy of the CSP list + does not affect the parent context --> +</head> +<body> + <script> + var t1 = async_test("Test that parent document image loads"); + var t2 = async_test("Test that embedded iframe document image does not load"); + var t3 = async_test("Test that spv event is fired"); + + window.onmessage = function(e) { + if (e.data.type == 'spv') { + t3.step(function() { + assert_equals(e.data.violatedDirective, "img-src"); + t3.done(); + }); + } else if (e.data.type == 'imgload') { + var img = document.createElement('img'); + img.src = "../support/pass.png"; + img.onload = function() { t1.done(); }; + img.onerror = t1.unreached_func('Should have loaded the image'); + document.body.appendChild(img); + + t2.step(function() { + assert_false(e.data.loaded, "Should not have loaded image inside the frame because of its CSP"); + t2.done(); + }); + } + } + + var srcdoc = ['<meta http-equiv="Content-Security-Policy" content="img-src \'none\'">', + '<script>', + ' window.addEventListener("securitypolicyviolation", function(e) {', + ' window.top.postMessage({type: "spv", violatedDirective: e.violatedDirective}, "*");', + ' });', + '</scr' + 'ipt>', + '<img src="../support/fail.png"', + ' onload="window.top.postMessage({type: \'imgload\', loaded: true}, \'*\')"', + ' onerror="window.top.postMessage({type: \'imgload\', loaded: false}, \'*\')">'].join('\n'); + var i = document.createElement('iframe'); + i.srcdoc = srcdoc; + document.body.appendChild(i); + </script> +</body> +</html> diff --git a/testing/web-platform/tests/content-security-policy/inheritance/javascript-url-open-in-main-window.html b/testing/web-platform/tests/content-security-policy/inheritance/javascript-url-open-in-main-window.html new file mode 100644 index 0000000000..2366284fc5 --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/javascript-url-open-in-main-window.html @@ -0,0 +1,13 @@ +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + async_test(t => { + window.addEventListener("message", t.step_func_done(e => { + assert_equals(e.data, "img blocked", + "Img should be blocked by CSP img-src 'none'"); + })); + + w = window.open("./support/navigate-self-to-javascript.html"); + t.add_cleanup(w.close); + }, "Executing Javascript URL keeps enforcing previous CSPs of the document."); +</script> diff --git a/testing/web-platform/tests/content-security-policy/inheritance/javascript-url-srcdoc-cross-origin-iframe-inheritance.html b/testing/web-platform/tests/content-security-policy/inheritance/javascript-url-srcdoc-cross-origin-iframe-inheritance.html new file mode 100644 index 0000000000..81210fe30f --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/javascript-url-srcdoc-cross-origin-iframe-inheritance.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<head> + <meta charset="utf-8"> + <title>Content Security Policy: nested inheritance</title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> +</head> +<body> + <script> + // This test creates a page with CSP: frame-src 'self'. The page is + // navigated to a javascript URL creating a cross-origin iframe inside a + // srcdoc iframe. If everything works correctly, the cross-origin iframe + // should be blocked. + // + // Note that most of the logic is performed by the iframe. This file is only + // for managing testharness assertions. + async_test(t => { + window.addEventListener("message", t.step_func(function(e) { + if (e.data === "frame allowed") { + assert_unreached("Frame should have been blocked."); + } else if (e.data === "frame blocked") { + t.done(); + } + })); + }, "Nested cross-origin iframe should be blocked by frame-src 'self'."); + </script> + <iframe src="./support/javascript-url-srcdoc-cross-origin-iframe-inheritance-helper.sub.html"></iframe> +</body> diff --git a/testing/web-platform/tests/content-security-policy/inheritance/location-reload.html b/testing/web-platform/tests/content-security-policy/inheritance/location-reload.html new file mode 100644 index 0000000000..5d68e381bc --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/location-reload.html @@ -0,0 +1,120 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<meta http-equiv="Content-Security-Policy" content="img-src 'none'"> +<body> +<script> + let message_from = (w, starts_with) => { + return new Promise(resolve => { + window.addEventListener('message', msg => { + if (msg.source == w) { + if (!starts_with || msg.data.startsWith(starts_with)) + resolve(msg.data); + } + }); + }); + }; + + const img_url = window.origin + "/content-security-policy/support/fail.png"; + const img_tag_string = ` + <img src="${img_url}" + onload="top.postMessage('img loaded', '*');" + onerror="top.postMessage('img blocked', '*');" + > + `; + + const html_test_payload = ` + <!doctype html> + <div>${img_tag_string}</div> + `; + let blob_url = URL.createObjectURL( + new Blob([html_test_payload], { type: 'text/html' })); + + let write_img_to_iframe = (iframe) => { + let div = iframe.contentDocument.createElement('div'); + div.innerHTML = img_tag_string; + iframe.contentDocument.body.appendChild(div); + }; + + + // Test location.reload() for "about:blank". + promise_test(async t => { + // Create an empty iframe. + window.iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + + // Add an img. + let message = message_from(iframe.contentWindow); + write_img_to_iframe(iframe); + + // Check that the empty document inherits CSP from the initiator. + assert_equals(await message, "img blocked", + "Image should be blocked by CSP inherited from the parent."); + + // Now perform a reload. + let message_2 = message_from(iframe.contentWindow); + let loaded = new Promise(resolve => iframe.onload = resolve); + iframe.contentWindow.location.reload(); + await loaded; + + // Add an img. + write_img_to_iframe(iframe); + + // Check that the empty document still has the right CSP after reload. + assert_equals(await message_2, "img blocked", + "Image should be blocked by CSP after reload."); + }, "location.reload() of empty iframe."); + + + // Test location.reload() for a blob URL. + promise_test(async t => { + // Create an iframe. + window.iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + + // Navigate to the blob URL. + let message = message_from(iframe.contentWindow); + iframe.contentWindow.location = blob_url; + + // Check that the blob URL inherits CSP from the initiator. + assert_equals(await message, "img blocked", + "Image should be blocked by CSP inherited from navigation initiator."); + + // Now perform a reload. + let message_2 = message_from(iframe.contentWindow); + let loaded = new Promise(resolve => iframe.onload = resolve); + iframe.contentWindow.location.reload(); + await loaded; + + // Check that the blob URL document still has the right CSP after reload. + assert_equals(await message_2, "img blocked", + "Image should be blocked by CSP after reload."); + }, "location.reload() of blob URL iframe."); + + + // Test location.reload() for a srcdoc iframe. + promise_test(async t => { + // Create a srcdoc iframe. + window.iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + + let message = message_from(iframe.contentWindow); + iframe.srcdoc = `${html_test_payload}`; + + // Check that the srcdoc iframe inherits from the parent. + assert_equals(await message, "img blocked", + "Image should be blocked by CSP inherited from navigation initiator."); + + // Now perform a reload. + let message_2 = message_from(iframe.contentWindow); + let loaded = new Promise(resolve => iframe.onload = resolve); + iframe.contentWindow.location.reload(); + await loaded; + + // Check that the srcdoc iframe still has the right CSP after reload. + assert_equals(await message_2, "img blocked", + "Image should be blocked by CSP after reload."); + }, "location.reload() of srcdoc iframe."); +</script> +</body> diff --git a/testing/web-platform/tests/content-security-policy/inheritance/sandboxed-blob-scheme.html b/testing/web-platform/tests/content-security-policy/inheritance/sandboxed-blob-scheme.html new file mode 100644 index 0000000000..590fa7ec1a --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/sandboxed-blob-scheme.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<html> + +<head> + <script nonce="abc" src="/resources/testharness.js"></script> + <script nonce="abc" src="/resources/testharnessreport.js"></script> +</head> + +<body> + <script nonce='abc'> + var blob_string = "<script>alert(document.domain)<\/scr"+"ipt>"; + var blob = new Blob([blob_string], {type : 'text/html'}); + var url = URL.createObjectURL(blob); + + var i = document.createElement('iframe'); + i.src = url; + i.sandbox = "allow-scripts"; + document.body.appendChild(i); + </script> + <script nonce='abc' async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=script-src%20%27nonce-abc%27'></script> +</body> + +</html> diff --git a/testing/web-platform/tests/content-security-policy/inheritance/sandboxed-blob-scheme.html.sub.headers b/testing/web-platform/tests/content-security-policy/inheritance/sandboxed-blob-scheme.html.sub.headers new file mode 100644 index 0000000000..adc398d890 --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/sandboxed-blob-scheme.html.sub.headers @@ -0,0 +1,5 @@ +Expires: Mon, 26 Jul 1997 05:00:00 GMT +Cache-Control: no-store, no-cache, must-revalidate +Pragma: no-cache +Set-Cookie: sandboxed-blob-scheme={{$id:uuid()}}; Path=/content-security-policy/inheritance/ +Content-Security-Policy: script-src 'nonce-abc'; report-uri http://{{host}}:{{ports[http][0]}}/reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/testing/web-platform/tests/content-security-policy/inheritance/sandboxed-data-scheme.html b/testing/web-platform/tests/content-security-policy/inheritance/sandboxed-data-scheme.html new file mode 100644 index 0000000000..b97bfb0c05 --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/sandboxed-data-scheme.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html> + +<head> + <script nonce="abc" src="/resources/testharness.js"></script> + <script nonce="abc" src="/resources/testharnessreport.js"></script> +</head> + +<body> + <script nonce='abc'> + var url = "data:text/html,<script>alert(document.domain)<\/scr"+"ipt>"; + + var i = document.createElement('iframe'); + i.src = url; + i.sandbox = "allow-scripts"; + document.body.appendChild(i); + </script> + <script nonce='abc' async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=script-src%20%27nonce-abc%27'></script> +</body> + +</html> diff --git a/testing/web-platform/tests/content-security-policy/inheritance/sandboxed-data-scheme.html.sub.headers b/testing/web-platform/tests/content-security-policy/inheritance/sandboxed-data-scheme.html.sub.headers new file mode 100644 index 0000000000..96da6514b8 --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/sandboxed-data-scheme.html.sub.headers @@ -0,0 +1,5 @@ +Expires: Mon, 26 Jul 1997 05:00:00 GMT +Cache-Control: no-store, no-cache, must-revalidate +Pragma: no-cache +Set-Cookie: sandboxed-data-scheme={{$id:uuid()}}; Path=/content-security-policy/inheritance/ +Content-Security-Policy: script-src 'nonce-abc'; report-uri http://{{host}}:{{ports[http][0]}}/reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/testing/web-platform/tests/content-security-policy/inheritance/support/empty.html b/testing/web-platform/tests/content-security-policy/inheritance/support/empty.html new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/support/empty.html diff --git a/testing/web-platform/tests/content-security-policy/inheritance/support/iframe-do.sub.html b/testing/web-platform/tests/content-security-policy/inheritance/support/iframe-do.sub.html new file mode 100644 index 0000000000..effc1adcdd --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/support/iframe-do.sub.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<meta http-equiv="Content-Security-Policy" content="img-src {{GET[img-src]}}"> +<script> + window.addEventListener('message', function(e) { + eval(e.data); + }); + top.postMessage('ready', '*'); +</script> diff --git a/testing/web-platform/tests/content-security-policy/inheritance/support/javascript-url-srcdoc-cross-origin-iframe-inheritance-helper.sub.html b/testing/web-platform/tests/content-security-policy/inheritance/support/javascript-url-srcdoc-cross-origin-iframe-inheritance-helper.sub.html new file mode 100644 index 0000000000..afe4753cf9 --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/support/javascript-url-srcdoc-cross-origin-iframe-inheritance-helper.sub.html @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<head> + <meta charset="utf-8"> + <meta http-equiv="Content-Security-Policy" content="frame-src 'self'"> + <script> + // The following is the content of a srcdoc iframe. It contains: + // - a script that catches the frame-src securitypolicyviolation event and + // forwards the information to the parent, + // - a cross-origin iframe. + let doc = ` + <script> + window.addEventListener("securitypolicyviolation", e => { + if (e.violatedDirective === "frame-src") { + window.top.postMessage("frame blocked", "*"); + } + }); + </scr` + `ipt> + <iframe src="http://{{hosts[alt][]}}:{{ports[http][0]}}/content-security-policy/inheritance/support/postmessage-top.html"></iframe>`; + doc = doc.replaceAll('"', "\\\'"); + + const js_url = "javascript:'<iframe srcdoc=\""+ doc +"\">'"; + window.open(js_url, "_self"); + </script> +</head> diff --git a/testing/web-platform/tests/content-security-policy/inheritance/support/message-opener-and-navigate-back.html b/testing/web-platform/tests/content-security-policy/inheritance/support/message-opener-and-navigate-back.html new file mode 100644 index 0000000000..75ee5bee7c --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/support/message-opener-and-navigate-back.html @@ -0,0 +1,5 @@ +<script> + const params = new URLSearchParams(window.location.search); + opener.postMessage({msg: "ready", token: params.get("token")}, "*"); + window.history.back(); +</script> diff --git a/testing/web-platform/tests/content-security-policy/inheritance/support/message-top-and-navigate-back.html b/testing/web-platform/tests/content-security-policy/inheritance/support/message-top-and-navigate-back.html new file mode 100644 index 0000000000..53d5a18cb3 --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/support/message-top-and-navigate-back.html @@ -0,0 +1,5 @@ +<script> + const params = new URLSearchParams(window.location.search); + top.postMessage({msg: "ready", token: params.get("token")}, "*"); + window.history.back(); +</script> diff --git a/testing/web-platform/tests/content-security-policy/inheritance/support/navigate-parent-to-blob.html b/testing/web-platform/tests/content-security-policy/inheritance/support/navigate-parent-to-blob.html new file mode 100644 index 0000000000..df4a443893 --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/support/navigate-parent-to-blob.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<html> + <head> + <meta http-equiv="Content-Security-Policy" content="script-src 'unsafe-inline'"> + </head> + <body> + <script> + const blob_payload = ` + <!doctype html> + <script> + var i = false; + try { + eval('i = true'); + } catch {} + opener.postMessage(i ? "eval allowed" : "eval blocked", '*'); + </scr` + `ipt> + `; + var blob_url = URL.createObjectURL( + new Blob([blob_payload], { type: 'text/html' })); + parent.location = blob_url; + </script> + </body> +</html> diff --git a/testing/web-platform/tests/content-security-policy/inheritance/support/navigate-self-to-blob.html b/testing/web-platform/tests/content-security-policy/inheritance/support/navigate-self-to-blob.html new file mode 100644 index 0000000000..9ea069969c --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/support/navigate-self-to-blob.html @@ -0,0 +1,6 @@ +<script nonce="abc"> + var blob_string = "<script>alert(document.domain)<\/script>"; + var blob = new Blob([blob_string], {type : 'text/html'}); + var url = URL.createObjectURL(blob); + location.href=url; +</script> diff --git a/testing/web-platform/tests/content-security-policy/inheritance/support/navigate-self-to-blob.html.sub.headers b/testing/web-platform/tests/content-security-policy/inheritance/support/navigate-self-to-blob.html.sub.headers new file mode 100644 index 0000000000..2642b0fa06 --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/support/navigate-self-to-blob.html.sub.headers @@ -0,0 +1,4 @@ +Expires: Mon, 26 Jul 1997 05:00:00 GMT +Cache-Control: no-store, no-cache, must-revalidate +Pragma: no-cache +Content-Security-Policy: {{GET[csp]}}; report-uri http://{{host}}:{{ports[http][0]}}/reporting/resources/report.py?op=put&reportID={{GET[report_id]}} diff --git a/testing/web-platform/tests/content-security-policy/inheritance/support/navigate-self-to-javascript.html b/testing/web-platform/tests/content-security-policy/inheritance/support/navigate-self-to-javascript.html new file mode 100644 index 0000000000..86ea60c283 --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/support/navigate-self-to-javascript.html @@ -0,0 +1,12 @@ +<meta http-equiv="Content-Security-Policy" content="img-src 'none'"/> +<script> + const js_payload = ` + <div> + <img src="${window.origin}/content-security-policy/support/fail.png" + onload="opener.postMessage(\\\'img loaded\\\', \\\'*\\\');" + onerror="opener.postMessage(\\\'img blocked\\\', \\\'*\\\');" + > + </div> + `; + open(`javascript:'${js_payload}'`,"_self"); +</script> diff --git a/testing/web-platform/tests/content-security-policy/inheritance/support/postmessage-opener.html b/testing/web-platform/tests/content-security-policy/inheritance/support/postmessage-opener.html new file mode 100644 index 0000000000..7ee11bc78d --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/support/postmessage-opener.html @@ -0,0 +1,4 @@ +<script> + const params = new URLSearchParams(window.location.search); + opener.postMessage({msg: "ready", token: params.get("token")}, "*"); +</script> diff --git a/testing/web-platform/tests/content-security-policy/inheritance/support/postmessage-top.html b/testing/web-platform/tests/content-security-policy/inheritance/support/postmessage-top.html new file mode 100644 index 0000000000..242063a80e --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/support/postmessage-top.html @@ -0,0 +1,5 @@ +<!DOCTYPE html> +<script> + const params = new URLSearchParams(window.location.search); + top.postMessage({msg: "ready", token: params.get("token")}, "*"); +</script> diff --git a/testing/web-platform/tests/content-security-policy/inheritance/support/srcdoc-child-frame.html b/testing/web-platform/tests/content-security-policy/inheritance/support/srcdoc-child-frame.html new file mode 100644 index 0000000000..9148be203d --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/support/srcdoc-child-frame.html @@ -0,0 +1,19 @@ +<head> + <meta http-equiv="Content-Security-Policy" content="img-src 'none'"> +</head> +<body> + <script> + var i = document.createElement('iframe'); + i.srcdoc=`<script> + window.addEventListener('securitypolicyviolation', function(e) { + if (e.violatedDirective == 'img-src') { + top.postMessage('img blocked', '*'); + } + }) + </scr` + `ipt> + <img src='/content-security-policy/support/fail.png' + onload='top.postMessage("img loaded", "*")'/>`; + i.id = "srcdoc-frame"; + document.body.appendChild(i); + </script> +</body> diff --git a/testing/web-platform/tests/content-security-policy/inheritance/unsandboxed-blob-scheme.html b/testing/web-platform/tests/content-security-policy/inheritance/unsandboxed-blob-scheme.html new file mode 100644 index 0000000000..cab192f836 --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/unsandboxed-blob-scheme.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html> + +<head> + <script nonce="abc" src="/resources/testharness.js"></script> + <script nonce="abc" src="/resources/testharnessreport.js"></script> +</head> + +<body> + <script nonce='abc'> + var blob_string = "<script>alert(document.domain)<\/scr"+"ipt>"; + var blob = new Blob([blob_string], {type : 'text/html'}); + var url = URL.createObjectURL(blob); + + var i = document.createElement('iframe'); + i.src = url; + document.body.appendChild(i); + </script> + <script nonce='abc' async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=script-src%20%27nonce-abc%27'></script> +</body> + +</html> diff --git a/testing/web-platform/tests/content-security-policy/inheritance/unsandboxed-blob-scheme.html.sub.headers b/testing/web-platform/tests/content-security-policy/inheritance/unsandboxed-blob-scheme.html.sub.headers new file mode 100644 index 0000000000..b1054d3506 --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/unsandboxed-blob-scheme.html.sub.headers @@ -0,0 +1,5 @@ +Expires: Mon, 26 Jul 1997 05:00:00 GMT +Cache-Control: no-store, no-cache, must-revalidate +Pragma: no-cache +Set-Cookie: unsandboxed-blob-scheme={{$id:uuid()}}; Path=/content-security-policy/inheritance/ +Content-Security-Policy: script-src 'nonce-abc'; report-uri http://{{host}}:{{ports[http][0]}}/reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/testing/web-platform/tests/content-security-policy/inheritance/unsandboxed-data-scheme.html b/testing/web-platform/tests/content-security-policy/inheritance/unsandboxed-data-scheme.html new file mode 100644 index 0000000000..a9d8e207dc --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/unsandboxed-data-scheme.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<html> + +<head> + <script nonce="abc" src="/resources/testharness.js"></script> + <script nonce="abc" src="/resources/testharnessreport.js"></script> +</head> + +<body> + <script nonce='abc'> + var url = "data:text/html,<script>alert(document.domain)<\/scri"+"pt>"; + + var i = document.createElement('iframe'); + i.src = url; + document.body.appendChild(i); + </script> + <script nonce='abc' async defer src='../support/checkReport.sub.js?reportField=violated-directive&reportValue=script-src%20%27nonce-abc%27'></script> +</body> + +</html> diff --git a/testing/web-platform/tests/content-security-policy/inheritance/unsandboxed-data-scheme.html.sub.headers b/testing/web-platform/tests/content-security-policy/inheritance/unsandboxed-data-scheme.html.sub.headers new file mode 100644 index 0000000000..f4a6088578 --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/unsandboxed-data-scheme.html.sub.headers @@ -0,0 +1,5 @@ +Expires: Mon, 26 Jul 1997 05:00:00 GMT +Cache-Control: no-store, no-cache, must-revalidate +Pragma: no-cache +Set-Cookie: unsandboxed-data-scheme={{$id:uuid()}}; Path=/content-security-policy/inheritance/ +Content-Security-Policy: script-src 'nonce-abc'; report-uri http://{{host}}:{{ports[http][0]}}/reporting/resources/report.py?op=put&reportID={{$id}} diff --git a/testing/web-platform/tests/content-security-policy/inheritance/window-open-local-after-network-scheme.sub.html b/testing/web-platform/tests/content-security-policy/inheritance/window-open-local-after-network-scheme.sub.html new file mode 100644 index 0000000000..0cdc03ce92 --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/window-open-local-after-network-scheme.sub.html @@ -0,0 +1,83 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/utils.js"></script> + +<meta http-equiv="Content-Security-Policy" content="img-src 'none'"> +<title>about:blank in popup inherits CSPs from the navigation initiator</title> +<body> + +<script> + const message_from = (source_token, w) => { + return new Promise(resolve => { + window.addEventListener('message', msg => { + if (msg.data.token === source_token) + resolve(msg.data.msg); + }); + }); + }; + + const testCases = [ + { + previous_origin: window.origin, + name: "Popup being navigated to about:blank was same-origin.", + }, + { + previous_origin: "http://{{hosts[alt][]}}:{{ports[http][0]}}", + name: "Popup being navigated to about:blank was cross-origin.", + }, + ]; + + testCases.forEach(testCase => { + promise_test(async t => { + // Create a popup and navigate it. + const popup_token = token(); + // const popup = window.open("about:blank", testCase.name); + const loaded = message_from(popup_token); + const popup = window.open( + testCase.previous_origin + + "/content-security-policy/inheritance/support" + + `/postmessage-opener.html?token=${popup_token}`, + testCase.name); + t.add_cleanup(() => popup.close()); + + assert_equals(await loaded, "ready"); + + // Navigate the popup to "about:blank". + window.open("about:blank", testCase.name); + await t.step_wait( + condition = () => { + try { + return popup.location.href == "about:blank"; + } catch {} + return false; + }, + description = "Wait for the popup to navigate.", + timeout=3000, + interval=50); + + // Now create an img in the popup and check if it is blocked by CSPs. + const script = popup.document.createElement('script'); + script.innerText = ` + function messageBack(msg) { + opener.postMessage(msg ,"*"); + } + `; + popup.document.head.appendChild(script); + const div = popup.document.createElement('div'); + + const img_token = token(); + const img_url = window.origin + "/content-security-policy/support/fail.png"; + div.innerHTML = ` + <img src="${img_url}" + onload="messageBack({msg: 'img loaded', token: '${img_token}'});" + onerror="messageBack({msg: 'img blocked', token: '${img_token}'});" + > + `; + + const msg = message_from(img_token); + popup.document.body.appendChild(div); + assert_equals(await msg, "img blocked"); + }, testCase.name); + }); +</script> diff --git a/testing/web-platform/tests/content-security-policy/inheritance/window.html b/testing/web-platform/tests/content-security-policy/inheritance/window.html new file mode 100644 index 0000000000..73def60ceb --- /dev/null +++ b/testing/web-platform/tests/content-security-policy/inheritance/window.html @@ -0,0 +1,66 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> + +<meta http-equiv="Content-Security-Policy" content="img-src 'none'"> + +<body> + +<script> + function wait_for_error_from_window(w, test) { + window.addEventListener('message', test.step_func(e => { + if (e.source != w) + return; + assert_equals(e.data, "error"); + w.close(); + test.done(); + })); + } + + async_test(t => { + var w = window.open(); + + var img = document.createElement('img'); + img.onerror = t.step_func_done(_ => w.close()); + img.onload = t.unreached_func(); + img.src = "/images/red-16x16.png"; + w.document.body.appendChild(img); + }, "window.open() inherits policy."); + + async_test(t => { + var w = window.open(); + + wait_for_error_from_window(w, t); + + w.document.write(` + <img src='/images/red-16x16.png' + onload='window.opener.postMessage("load", "*");' + onerror='window.opener.postMessage("error", "*");' + > + `); + }, "`document.write` into `window.open()` inherits policy."); + + async_test(t => { + var b = new Blob( + [` + <img src='${window.origin}/images/red-16x16.png' + onload='window.opener.postMessage("load", "*");' + onerror='window.opener.postMessage("error", "*");' + > + `], {type:"text/html"}); + + wait_for_error_from_window(window.open(URL.createObjectURL(b)), t); + }, "window.open('blob:...') inherits policy."); + + // Navigation to top-level `data:` is blocked. + + async_test(t => { + var url = + `javascript:"<img src='${window.origin}/images/red-16x16.png' + onload='window.opener.postMessage(\\"load\\", \\"*\\");' + onerror='window.opener.postMessage(\\"error\\", \\"*\\");' + >"`; + + wait_for_error_from_window(window.open(url), t); + }, "window.open('javascript:...') inherits policy."); +</script> |