diff options
Diffstat (limited to 'testing/web-platform/tests/trusted-types/block-text-node-insertion-into-script-element.html')
-rw-r--r-- | testing/web-platform/tests/trusted-types/block-text-node-insertion-into-script-element.html | 238 |
1 files changed, 238 insertions, 0 deletions
diff --git a/testing/web-platform/tests/trusted-types/block-text-node-insertion-into-script-element.html b/testing/web-platform/tests/trusted-types/block-text-node-insertion-into-script-element.html new file mode 100644 index 0000000000..08e3e69553 --- /dev/null +++ b/testing/web-platform/tests/trusted-types/block-text-node-insertion-into-script-element.html @@ -0,0 +1,238 @@ +<!DOCTYPE html> +<html> +<head> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> + <meta http-equiv="Content-Security-Policy" content="require-trusted-types-for 'script';"> +</head> +<body> +<div id="container"></div> +<script id="script1">"hello world!";</script> +<script id="trigger"></script> +<script> + var container = document.querySelector("#container"); + const policy_dict = { + createScript: s => (s.includes("fail") ? null : s.replace("default", "count")), + createHTML: s => s.replace(/2/g, "3"), + }; + const policy = trustedTypes.createPolicy("policy", policy_dict); + + // Regression tests for 'Bypass via insertAdjacentText', reported at + // https://github.com/w3c/trusted-types/issues/133 + + // We are trying to assert that scripts do _not_ get executed. We + // accomplish by having the script under examination containing a + // postMessage, and to send a second guaranteed-to-execute postMessage + // so there's a point in time when we're sure the first postMessage + // must have arrived (if indeed it had been sent). + // + // We'll interpret the message data as follows: + // - includes "block": error (this message should have been blocked by TT) + // - includes "count": Count these, and later check against expect_count. + // - includes "done": Unregister the event handler and finish the test. + // - all else: Reject, as this is probably an error in the test. + function checkMessage(expect_count) { + postMessage("done", "*"); + return new Promise((resolve, reject) => { + let count = 0; + globalThis.addEventListener("message", function handler(e) { + if (e.data.includes("block")) { + reject(`'block' received (${e.data})`); + } else if (e.data.includes("count")) { + count = count + 1; + } else if (e.data.includes("done")) { + globalThis.removeEventListener("message", handler); + if (expect_count && count != expect_count) { + reject( + `'done' received, but unexpected counts: expected ${expect_count} != actual ${count} (${e.data})`); + } else { + resolve(e.data); + } + } else { + reject("unexpected message received: " + e.data); + } + }); + }); + } + + function checkSecurityPolicyViolationEvent(expect_count) { + return new Promise((resolve, reject) => { + let count = 0; + document.addEventListener("securitypolicyviolation", e => { + if (e.sample.includes("trigger")) { + if (expect_count && count != expect_count) { + reject( + `'trigger' received, but unexpected counts: expected ${expect_count} != actual ${count}`); + } else { + resolve(); + } + } else { + count = count + 1; + } + }); + try { + document.getElementById("trigger").text = "trigger fail"; + } catch(e) { } + }); + } + + // Original report: + promise_test(t => { + // Setup: Create a <script> element with a <p> child. + let s = document.createElement("script"); + + // Sanity check: Element child content (<p>) doesn't count as source text. + let p = document.createElement("p"); + p.text = "hello('world');"; + s.appendChild(p); + container.appendChild(s); + assert_equals(s.text, ""); + + // Try to insertAdjacentText into the <script>, starting from the <p> + p.insertAdjacentText("beforebegin", + "postMessage('beforebegin should be blocked', '*');"); + assert_true(s.text.includes("postMessage")); + p.insertAdjacentText("afterend", + "postMessage('afterend should be blocked', '*');"); + assert_true(s.text.includes("after")); + return checkMessage(); + }, "Regression test: Bypass via insertAdjacentText, initial comment."); + + const script_elements = { + "script": doc => doc.createElement("script"), + "svg:script": doc => doc.createElementNS("http://www.w3.org/2000/svg", "script"), + }; + for (let [element, create_element] of Object.entries(script_elements)) { + // Like the "Original Report" test case above, but avoids use of the "text" + // accessor that <svg:script> doesn't support. + promise_test(t => { + let s = create_element(document); + + // Sanity check: Element child content (<p>) doesn't count as source text. + let p = document.createElement("p"); + p.textContent = "hello('world');"; + s.appendChild(p); + container.appendChild(s); + + // Try to insertAdjacentText into the <script>, starting from the <p> + p.insertAdjacentText("beforebegin", + "postMessage('beforebegin should be blocked', '*');"); + assert_true(s.textContent.includes("postMessage")); + p.insertAdjacentText("afterend", + "postMessage('afterend should be blocked', '*');"); + assert_true(s.textContent.includes("after")); + return checkMessage(); + }, "Regression test: Bypass via insertAdjacentText, initial comment. " + element); + + // Variant: Create a <script> element and create & insert a text node. Then + // insert it into the document container to make it live. + promise_test(t => { + // Setup: Create a <script> element, and insert text via a text node. + let s = create_element(document); + let text = document.createTextNode("postMessage('appendChild with a " + + "text node should be blocked', '*');"); + s.appendChild(text); + container.appendChild(s); + return checkMessage(); + }, "Regression test: Bypass via appendChild into off-document script element" + element); + + // Variant: Create a <script> element and insert it into the document. Then + // create a text node and insert it into the live <script> element. + promise_test(t => { + // Setup: Create a <script> element, insert it into the doc, and then create + // and insert text via a text node. + let s = create_element(document); + container.appendChild(s); + let text = document.createTextNode("postMessage('appendChild with a live " + + "text node should be blocked', '*');"); + s.appendChild(text); + return checkMessage(); + }, "Regression test: Bypass via appendChild into live script element " + element); + } + + promise_test(t => { + return checkSecurityPolicyViolationEvent(); + }, "Prep for subsequent tests: Clear SPV event queue."); + + promise_test(t => { + const inserted_script = document.getElementById("script1"); + assert_throws_js(TypeError, _ => { + inserted_script.innerHTML = "2+2"; + }); + + let new_script = document.createElement("script"); + assert_throws_js(TypeError, _ => { + new_script.innerHTML = "2+2"; + container.appendChild(new_script); + }); + + const trusted_html = policy.createHTML("2*4"); + assert_equals("" + trusted_html, "3*4"); + inserted_script.innerHTML = trusted_html; + assert_equals(inserted_script.textContent, "3*4"); + + new_script = document.createElement("script"); + new_script.innerHTML = trusted_html; + container.appendChild(new_script); + assert_equals(inserted_script.textContent, "3*4"); + + // We expect 3 SPV events: two for the two assert_throws_js cases, and one + // for script element, which will be rejected at the time of execution. + return checkSecurityPolicyViolationEvent(3); + }, "Spot tests around script + innerHTML interaction."); + + + // Create default policy. Wrapped in a promise_test to ensure it runs only + // after the other tests. + let default_policy; + promise_test(t => { + default_policy = trustedTypes.createPolicy("default", policy_dict); + return Promise.resolve(); + }, "Prep for subsequent tests: Create default policy."); + + for (let [element, create_element] of Object.entries(script_elements)) { + promise_test(t => { + let s = create_element(document); + let text = document.createTextNode("postMessage('default', '*');"); + s.appendChild(text); + container.appendChild(s); + return Promise.all([checkMessage(1), + checkSecurityPolicyViolationEvent(0)]); + }, "Test that default policy applies. " + element); + + promise_test(t => { + let s = create_element(document); + let text = document.createTextNode("fail"); + s.appendChild(text); + container.appendChild(s); + return Promise.all([checkMessage(0), + checkSecurityPolicyViolationEvent(1)]); + }, "Test a failing default policy. " + element); + } + + promise_test(t => { + const inserted_script = document.getElementById("script1"); + inserted_script.innerHTML = "2+2"; + assert_equals(inserted_script.textContent, "3+3"); + + let new_script = document.createElement("script"); + new_script.innerHTML = "2+2"; + container.appendChild(new_script); + assert_equals(inserted_script.textContent, "3+3"); + + const trusted_html = default_policy.createHTML("2*4"); + assert_equals("" + trusted_html, "3*4"); + inserted_script.innerHTML = trusted_html; + assert_equals(inserted_script.textContent, "3*4"); + + new_script = document.createElement("script"); + new_script.innerHTML = trusted_html; + container.appendChild(new_script); + assert_equals(inserted_script.textContent, "3*4"); + + return checkSecurityPolicyViolationEvent(0); + }, "Spot tests around script + innerHTML interaction with default policy."); +</script> +</body> +</html> + |