summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/crbug-583445-regression.window.js
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--testing/web-platform/tests/html/webappapis/dynamic-markup-insertion/opening-the-input-stream/crbug-583445-regression.window.js127
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");
+