diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /testing/web-platform/tests/fetch/security/dangling-markup | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/fetch/security/dangling-markup')
6 files changed, 551 insertions, 0 deletions
diff --git a/testing/web-platform/tests/fetch/security/dangling-markup/dangling-markup-mitigation-data-url.tentative.sub.html b/testing/web-platform/tests/fetch/security/dangling-markup/dangling-markup-mitigation-data-url.tentative.sub.html new file mode 100644 index 0000000000..f27735daa1 --- /dev/null +++ b/testing/web-platform/tests/fetch/security/dangling-markup/dangling-markup-mitigation-data-url.tentative.sub.html @@ -0,0 +1,229 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> + function readableURL(url) { + return url.replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t"); + } + + // For each of the following tests, we'll inject a frame containing the HTML we'd like to poke at + // as a `srcdoc` attribute. Because we're injecting markup via `srcdoc`, we need to entity-escape + // the content we'd like to treat as "raw" (e.g. `\n` => ` `, `<` => `<`), and + // double-escape the "escaped" content. + var rawBrace = "<"; + var escapedBrace = "&lt;"; + var doubleEscapedBrace = "&amp;lt;"; + var rawNewline = " "; + var escapedNewline = "&#10;"; + // doubleEscapedNewline is used inside a data URI, and so must have its '#' escaped. + var doubleEscapedNewline = "&amp;%2310;"; + + function appendFrameAndGetElement(test, frame) { + return new Promise((resolve, reject) => { + frame.onload = test.step_func(_ => { + frame.onload = null; + resolve(frame.contentDocument.querySelector('#dangling')); + }); + document.body.appendChild(frame); + }); + } + + function assert_img_loaded(test, frame) { + appendFrameAndGetElement(test, frame) + .then(test.step_func_done(img => { + assert_equals(img.naturalHeight, 1, "Height"); + frame.remove(); + })); + } + + function assert_img_not_loaded(test, frame) { + appendFrameAndGetElement(test, frame) + .then(test.step_func_done(img => { + assert_equals(img.naturalHeight, 0, "Height"); + assert_equals(img.naturalWidth, 0, "Width"); + })); + } + + function assert_nested_img_not_loaded(test, frame) { + window.addEventListener('message', test.step_func(e => { + if (e.source != frame.contentWindow) + return; + + assert_equals(e.data, 'error'); + test.done(); + })); + appendFrameAndGetElement(test, frame); + } + + function assert_nested_img_loaded(test, frame) { + window.addEventListener('message', test.step_func(e => { + if (e.source != frame.contentWindow) + return; + + assert_equals(e.data, 'loaded'); + test.done(); + })); + appendFrameAndGetElement(test, frame); + } + + function createFrame(markup) { + var i = document.createElement('iframe'); + i.srcdoc = `${markup}sekrit`; + return i; + } + + // Subresource requests: + [ + // Data URLs don't themselves trigger blocking: + `<img id="dangling" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=">`, + `<img id="dangling" src="data:image/png;base64,${rawNewline}iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=">`, + `<img id="dangling" src="data:image/png;base64,i${rawNewline}VBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=">`, + + // Data URLs with visual structure don't trigger blocking + `<img id="dangling" src="data:image/svg+xml;utf8, + <svg width='1' height='1' xmlns='http://www.w3.org/2000/svg'> + <rect width='100%' height='100%' fill='rebeccapurple'/> + <rect x='10%' y='10%' width='80%' height='80%' fill='lightgreen'/> + </svg>">` + ].forEach(markup => { + async_test(t => { + var i = createFrame(`${markup} <element attr="" another=''>`); + assert_img_loaded(t, i); + }, readableURL(markup)); + }); + + // Nested subresource requests: + // + // The following tests load a given HTML string into `<iframe srcdoc="...">`, so we'll + // end up with a frame with an ID of `dangling` inside the srcdoc frame. That frame's + // `src` is a `data:` URL that resolves to an HTML document containing an `<img>`. The + // error/load handlers on that image are piped back up to the top-level document to + // determine whether the tests' expectations were met. *phew* + + // Allowed: + [ + // Just a newline: + `<iframe id="dangling" + src="data:text/html, + <img + onload='window.parent.postMessage("loaded", "*");' + onerror='window.parent.postMessage("error", "*");' + src='http://{{host}}:{{ports[http][0]}}/images/gr${rawNewline}een-256x256.png'> + "> + </iframe>`, + + // Just a brace: + `<iframe id="dangling" + src="data:text/html, + <img + onload='window.parent.postMessage("loaded", "*");' + onerror='window.parent.postMessage("error", "*");' + src='http://{{host}}:{{ports[http][0]}}/images/green-256x256.png?${rawBrace}'> + "> + </iframe>`, + + // Newline and escaped brace. + `<iframe id="dangling" + src="data:text/html, + <img + onload='window.parent.postMessage("loaded", "*");' + onerror='window.parent.postMessage("error", "*");' + src='http://{{host}}:{{ports[http][0]}}/images/gr${rawNewline}een-256x256.png?${doubleEscapedBrace}'> + "> + </iframe>`, + + // Brace and escaped newline: + `<iframe id="dangling" + src="data:text/html, + <img + onload='window.parent.postMessage("loaded", "*");' + onerror='window.parent.postMessage("error", "*");' + src='http://{{host}}:{{ports[http][0]}}/images/green-256x256.png?${doubleEscapedNewline}${rawBrace}'> + "> + </iframe>`, + ].forEach(markup => { + async_test(t => { + var i = createFrame(` + <script> + // Repeat the message so that the parent can track this frame as the source. + window.onmessage = e => window.parent.postMessage(e.data, '*'); + </scr`+`ipt> + ${markup} + `); + assert_nested_img_loaded(t, i); + }, readableURL(markup)); + }); + + // Nested requests that should fail: + [ + // Newline and brace: + `<iframe id="dangling" + src="data:text/html, + <img + onload='window.parent.postMessage("loaded", "*");' + onerror='window.parent.postMessage("error", "*");' + src='http://{{host}}:{{ports[http][0]}}/images/gr${rawNewline}een-256x256.png?${rawBrace}'> + "> + </iframe>`, + + // Leading whitespace: + `<iframe id="dangling" + src=" data:text/html, + <img + onload='window.parent.postMessage("loaded", "*");' + onerror='window.parent.postMessage("error", "*");' + src='http://{{host}}:{{ports[http][0]}}/images/gr${rawNewline}een-256x256.png?${rawBrace}'> + "> + </iframe>`, + + // Leading newline: + `<iframe id="dangling" + src="\ndata:text/html, + <img + onload='window.parent.postMessage("loaded", "*");' + onerror='window.parent.postMessage("error", "*");' + src='http://{{host}}:{{ports[http][0]}}/images/gr${rawNewline}een-256x256.png?${rawBrace}'> + "> + </iframe>`, + `<iframe id="dangling" + src="${rawNewline}data:text/html, + <img + onload='window.parent.postMessage("loaded", "*");' + onerror='window.parent.postMessage("error", "*");' + src='http://{{host}}:{{ports[http][0]}}/images/gr${rawNewline}een-256x256.png?${rawBrace}'> + "> + </iframe>`, + + // Leading tab: + `<iframe id="dangling" + src="\tdata:text/html, + <img + onload='window.parent.postMessage("loaded", "*");' + onerror='window.parent.postMessage("error", "*");' + src='http://{{host}}:{{ports[http][0]}}/images/gr${rawNewline}een-256x256.png?${rawBrace}'> + "> + </iframe>`, + + // Leading carrige return: + `<iframe id="dangling" + src="\rdata:text/html, + <img + onload='window.parent.postMessage("loaded", "*");' + onerror='window.parent.postMessage("error", "*");' + src='http://{{host}}:{{ports[http][0]}}/images/gr${rawNewline}een-256x256.png?${rawBrace}'> + "> + </iframe>`, + ].forEach(markup => { + async_test(t => { + var i = createFrame(` + <script> + // Repeat the message so that the parent can track this frame as the source. + window.onmessage = e => window.parent.postMessage(e.data, '*'); + </scr`+`ipt> + ${markup} + `); + assert_nested_img_not_loaded(t, i); + }, readableURL(markup)); + }); +</script> diff --git a/testing/web-platform/tests/fetch/security/dangling-markup/dangling-markup-mitigation.tentative.html b/testing/web-platform/tests/fetch/security/dangling-markup/dangling-markup-mitigation.tentative.html new file mode 100644 index 0000000000..61a931608b --- /dev/null +++ b/testing/web-platform/tests/fetch/security/dangling-markup/dangling-markup-mitigation.tentative.html @@ -0,0 +1,147 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> + function readableURL(url) { + return url.replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t"); + } + + var should_load = [ + `/images/green-1x1.png`, + `/images/gre\nen-1x1.png`, + `/images/gre\ten-1x1.png`, + `/images/gre\ren-1x1.png`, + `/images/green-1x1.png?img=<`, + `/images/green-1x1.png?img=<`, + `/images/green-1x1.png?img=%3C`, + `/images/gr\neen-1x1.png?img=%3C`, + `/images/gr\reen-1x1.png?img=%3C`, + `/images/gr\teen-1x1.png?img=%3C`, + `/images/green-1x1.png?img= `, + `/images/gr\neen-1x1.png?img= `, + `/images/gr\reen-1x1.png?img= `, + `/images/gr\teen-1x1.png?img= `, + ]; + should_load.forEach(url => async_test(t => { + fetch(url) + .then(t.step_func_done(r => { + assert_equals(r.status, 200); + })) + .catch(t.unreached_func("Fetch should succeed.")); + }, "Fetch: " + readableURL(url))); + + var should_block = [ + `/images/gre\nen-1x1.png?img=<`, + `/images/gre\ren-1x1.png?img=<`, + `/images/gre\ten-1x1.png?img=<`, + `/images/green-1x1.png?<\n=block`, + `/images/green-1x1.png?<\r=block`, + `/images/green-1x1.png?<\t=block`, + ]; + should_block.forEach(url => async_test(t => { + fetch(url) + .then(t.unreached_func("Fetch should fail.")) + .catch(t.step_func_done()); + }, "Fetch: " + readableURL(url))); + + + // For each of the following tests, we'll inject a frame containing the HTML we'd like to poke at + // as a `srcdoc` attribute. Because we're injecting markup via `srcdoc`, we need to entity-escape + // the content we'd like to treat as "raw" (e.g. `\n` => ` `, `<` => `<`), and + // double-escape the "escaped" content. + var rawBrace = "<"; + var escapedBrace = "&lt;"; + var rawNewline = " "; + var escapedNewline = "&#10;"; + + function appendFrameAndGetElement(test, frame) { + return new Promise((resolve, reject) => { + frame.onload = test.step_func(_ => { + frame.onload = null; + resolve(frame.contentDocument.querySelector('#dangling')); + }); + document.body.appendChild(frame); + }); + } + + function assert_img_loaded(test, frame) { + appendFrameAndGetElement(test, frame) + .then(test.step_func_done(img => { + assert_equals(img.naturalHeight, 1, "Height"); + frame.remove(); + })); + } + + function assert_img_not_loaded(test, frame) { + appendFrameAndGetElement(test, frame) + .then(test.step_func_done(img => { + assert_equals(img.naturalHeight, 0, "Height"); + assert_equals(img.naturalWidth, 0, "Width"); + })); + } + + function createFrame(markup) { + var i = document.createElement('iframe'); + i.srcdoc = `${markup}sekrit`; + return i; + } + + // The following resources should not be blocked, as their URLs do not contain both a `\n` and `<` + // character in the body of the URL. + var should_load = [ + // Brace alone doesn't block: + `<img id="dangling" src="/images/green-1x1.png?img=${rawBrace}b">`, + + // Newline alone doesn't block: + `<img id="dangling" src="/images/green-1x1.png?img=${rawNewline}b">`, + + // Entity-escaped characters don't trigger blocking: + `<img id="dangling" src="/images/green-1x1.png?img=${escapedNewline}b">`, + `<img id="dangling" src="/images/green-1x1.png?img=${escapedBrace}b">`, + `<img id="dangling" src="/images/green-1x1.png?img=${escapedNewline}b${escapedBrace}c">`, + + // Leading and trailing whitespace is stripped: + ` + <img id="dangling" src=" + /images/green-1x1.png?img= + "> + `, + ` + <img id="dangling" src=" + /images/green-1x1.png?img=${escapedBrace} + "> + `, + ` + <img id="dangling" src=" + /images/green-1x1.png?img=${escapedNewline} + "> + `, + ]; + + should_load.forEach(markup => { + async_test(t => { + var i = createFrame(`${markup} <element attr="" another=''>`); + assert_img_loaded(t, i); + }, readableURL(markup)); + }); + + // The following resources should be blocked, as their URLs contain both `\n` and `<` characters: + var should_block = [ + `<img id="dangling" src="/images/green-1x1.png?img=${rawNewline}${rawBrace}b">`, + `<img id="dangling" src="/images/green-1x1.png?img=${rawBrace}${rawNewline}b">`, + ` + <img id="dangling" src="/images/green-1x1.png?img= + ${rawBrace} + ${rawNewline}b + "> + `, + ]; + + should_block.forEach(markup => { + async_test(t => { + var i = createFrame(`${markup}`); + assert_img_not_loaded(t, i); + }, readableURL(markup)); + }); +</script> diff --git a/testing/web-platform/tests/fetch/security/dangling-markup/media.html b/testing/web-platform/tests/fetch/security/dangling-markup/media.html new file mode 100644 index 0000000000..2649edcf32 --- /dev/null +++ b/testing/web-platform/tests/fetch/security/dangling-markup/media.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> + var resources = {"audio": "/media/sound_5.mp3", "video":"/media/test.mp4"}; + + for (const key in resources){ + async_test(t => { + let elem = document.body.appendChild(document.createElement(key)); + elem.onerror = t.unreached_func(`${key} should load`); + elem.oncanplay = t.step_func(() => { + t.done(); + }); + elem.src = resources[key]; + }, `Should load ${key}`); + + async_test(t => { + let elem = document.body.appendChild(document.createElement(key)); + elem.onerror = t.step_func(() => { + t.done(); + }); + elem.oncanplay = t.unreached_func(`${key} should not load`); + elem.src = resources[key] + "?\n<"; + }, `Should not load ${key} with dangling markup in URL`); + } +</script> diff --git a/testing/web-platform/tests/fetch/security/dangling-markup/option.html b/testing/web-platform/tests/fetch/security/dangling-markup/option.html new file mode 100644 index 0000000000..f528bed999 --- /dev/null +++ b/testing/web-platform/tests/fetch/security/dangling-markup/option.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="./resources/helper.js"></script> +<body> +<script> + + var tests = [ + ` + <form action="/resource-timing/resources/document-navigated.html" method="post"> + <input type="submit"> + <select name="dangling"><option> + `, + ` + <div> + <form action="/resource-timing/resources/document-navigated.html" method="post"> + <input type="submit"> + <select name="dangling"><option> + `, + ` + <form action="/resource-timing/resources/document-navigated.html" method="post" id="form"> + <input type="submit"> + </form> + <select name="dangling" form="form"><option> + `, + ` + <form action="/resource-timing/resources/document-navigated.html" method="post"> + <input type="submit"> + <select name="dangling"><option label="yay"> + `, + ` + <div> + <form action="/resource-timing/resources/document-navigated.html" method="post"> + <input type="submit"> + <select name="dangling"><option label="yay"> + `, + ` + <form action="/resource-timing/resources/document-navigated.html" method="post" id="form"> + <input type="submit"> + </form> + <select name="dangling" form="form"><option label="yay"> + ` + ]; + + tests.forEach(markup => { + async_test(t => { + var i = createFrame(`${markup}sekrit<element attribute></element>`); + assert_no_submission(t, i); + }, markup.replace(/[\n\r]/g, '')); + }); +</script> diff --git a/testing/web-platform/tests/fetch/security/dangling-markup/resources/helper.js b/testing/web-platform/tests/fetch/security/dangling-markup/resources/helper.js new file mode 100644 index 0000000000..100bcba7b5 --- /dev/null +++ b/testing/web-platform/tests/fetch/security/dangling-markup/resources/helper.js @@ -0,0 +1,63 @@ +function assert_no_message_from_frame(test, frame) { + window.addEventListener("message", test.step_func(e => { + assert_not_equals(e.source, frame.contentWindow); + })); +} + +function appendFrameAndGetElement(test, frame) { + return new Promise((resolve, reject) => { + frame.onload = test.step_func(_ => { + frame.onload = null; + resolve(frame.contentDocument.querySelector('#dangling')); + }); + document.body.appendChild(frame); + }); +} + +function appendAndSubmit(test, frame) { + return new Promise((resolve, reject) => { + frame.onload = test.step_func(_ => { + frame.onload = null; + frame.contentDocument.querySelector('form').addEventListener("error", _ => { + resolve("error"); + }); + frame.contentDocument.querySelector('form').addEventListener("submit", _ => { + resolve("submit"); + }); + frame.contentDocument.querySelector('[type=submit]').click(); + }); + document.body.appendChild(frame); + }); +} + +function assert_no_submission(test, frame) { + assert_no_message_from_frame(test, frame); + + appendAndSubmit(test, frame) + .then(test.step_func_done(result => { + assert_equals(result, "error"); + frame.remove(); + })); +} + +function assert_img_loaded(test, frame) { + appendFrameAndGetElement(test, frame) + .then(test.step_func_done(img => { + assert_equals(img.naturalHeight, 103, "Height"); + assert_equals(img.naturalWidth, 76, "Width"); + })); +} + +function assert_img_not_loaded(test, frame) { + appendFrameAndGetElement(test, frame) + .then(test.step_func_done(img => { + assert_equals(img.naturalHeight, 0, "Height"); + assert_equals(img.naturalWidth, 0, "Width"); + })); +} + +function createFrame(markup) { + var i = document.createElement('iframe'); + i.srcdoc = `${markup}sekrit`; + return i; +} diff --git a/testing/web-platform/tests/fetch/security/dangling-markup/textarea.html b/testing/web-platform/tests/fetch/security/dangling-markup/textarea.html new file mode 100644 index 0000000000..c4b334edc9 --- /dev/null +++ b/testing/web-platform/tests/fetch/security/dangling-markup/textarea.html @@ -0,0 +1,34 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="./resources/helper.js"></script> +<body> +<script> + + var tests = [ + ` + <form action="/resource-timing/resources/document-navigated.html" method="post"> + <input type="submit"> + <textarea name="dangling"> + `, + ` + <div> + <form action="/resource-timing/resources/document-navigated.html" method="post"> + <input type="submit"> + <textarea name="dangling"> + `, + ` + <form action="/resource-timing/resources/document-navigated.html" method="post" id="form"> + <input type="submit"> + </form> + <textarea name="dangling" form="form"> + ` + ]; + + tests.forEach(markup => { + async_test(t => { + var i = createFrame(`${markup}sekrit<element attribute></element>`); + assert_no_submission(t, i); + }, markup.replace(/[\n\r]/g, '')); + }); +</script> |