diff options
Diffstat (limited to '')
-rw-r--r-- | testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/crbug-583445-regression.window.js | 127 |
1 files changed, 127 insertions, 0 deletions
diff --git a/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/crbug-583445-regression.window.js b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/crbug-583445-regression.window.js new file mode 100644 index 0000000000..3809c2e081 --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/crbug-583445-regression.window.js @@ -0,0 +1,127 @@ +// META: script=/common/get-host-info.sub.js +// META: script=/common/utils.js +// META: script=/common/dispatcher/dispatcher.js +// +// This is a regression test for crbug.com/583445. It checks an obscure bug in +// Chromium's handling of `document.open()` whereby the URL change would affect +// the document's origin after a javascript navigation. +// +// See also dcheng@'s comments on the original code review in which he +// introduced the precursor to this test: +// https://codereview.chromium.org/1675473002. + +function nextMessage() { + return new Promise((resolve) => { + window.addEventListener("message", (e) => { resolve(e.data); }, { + once: true + }); + }); +} + +promise_test(async (t) => { + // Embed a cross-origin frame A and set up remote code execution. + const iframeA = document.body.appendChild(document.createElement("iframe")); + t.add_cleanup(() => { iframeA.remove(); }); + + const uuidA = token(); + iframeA.src = remoteExecutorUrl(uuidA, { host: get_host_info().REMOTE_HOST }); + const ctxA = new RemoteContext(uuidA); + + // Frame A embeds a cross-origin frame B, which is same-origin with the + // top-level frame. Frame B is the center of this test: it is where we will + // verify that a bug does not grant it UXSS in frame A. + // + // Though we could reach into `iframeA.frames[0]` to get a proxy to frame B + // and use `setTimeout()` like below to execute code inside it, we set up + // remote code execution using `dispatcher.js` for better ergonomics. + const uuidB = token(); + await ctxA.execute_script((url) => { + const iframeB = document.createElement("iframe"); + iframeB.src = url; + document.body.appendChild(iframeB); + }, [remoteExecutorUrl(uuidB).href]); + + // Start listening for a message, which will come as a result of executing + // the code below in frame B. + const message = nextMessage(); + + const ctxB = new RemoteContext(uuidB); + await ctxB.execute_script(() => { + // Frame B embeds an `about:blank` frame C. + const iframeC = document.body.appendChild(document.createElement("iframe")); + + // We wish to execute code inside frame C, but it is important to this test + // that its URL remain `about:blank`, so we cannot use `dispatcher.js`. + // Instead we rely on `setTimeout()`. + // + // We use `setTimeout(string, ...)` instead of `setTimeout(function, ...)` + // as the given script executes against the target window's global object + // and does not capture any local variables. + // + // In order to have nice syntax highlighting and avoid quote-escaping hell, + // we use a trick employed by `dispatcher.js`. We rely on the fact that + // functions in JS have a stringifier that returns their source code. Thus + // `"(" + func + ")()"` is a string that executes `func()` when evaluated. + iframeC.contentWindow.setTimeout("(" + (() => { + // This executes in frame C. + + // Frame C calls `document.open()` on its parent, which results in B's + // URL being set to `about:blank` (C's URL). + // + // However, just before `document.open()` is called, B schedules a + // self-navigation to a `javascript:` URL. This will occur after + // `document.open()`, so the document will navigate from `about:blank` to + // the new URL. + // + // This should not result in B's origin changing, so B should remain + // same-origin with the top-level frame. + // + // Due to crbug.com/583445, this used to behave wrongly in Chromium. The + // navigation code incorrectly assumed that B's origin should be inherited + // from its parent A because B's URL was `about:blank`. + // + // It is important to schedule this from within the child, as this + // guarantees that `document.open()` will be called before the navigation. + // A previous version of this test scheduled this from within frame B + // right after scheduling the call to `document.open()`, but that ran the + // risk of races depending on which timeout fired first. + parent.window.setTimeout("(" + (() => { + // This executes in frame B. + + location = "javascript:(" + (() => { + /* This also executes in frame B. + * + * Note that because this whole function gets stuffed in a JS URL, + * single-line comments do not work, as they affect the following + * lines. */ + + let error; + try { + /* This will fail with a `SecurityError` if frame B is no longer + * same-origin with the top-level frame. */ + top.window.testSameOrigin = true; + } catch (e) { + error = e; + } + + top.postMessage({ + error: error?.toString(), + }, "*"); + + }) + ")()"; + + }) + ")()", 0); + + // This executes in frame C. + parent.document.open(); + + }) + ")()", 0); + }); + + // Await the message from frame B after its navigation. + const { error } = await message; + assert_equals(error, undefined, "error accessing top frame from frame B"); + assert_true(window.testSameOrigin, "top frame testSameOrigin is mutated"); + +}, "Regression test for crbug.com/583445"); + |