From 40807ad2ea6d0ae8739048203c17fdf657b61334 Mon Sep 17 00:00:00 2001 From: Nika Layzell Date: Thu, 15 Jul 2021 21:09:16 +0000 Subject: [PATCH] Bug 1715167 - Part 7: Add tests for precursor principals, r=ckerschb,ngogge These test various ways of loading documents which will end up with null principals, and verify that they are loaded with the expected precursor URI. Depends on D119693 Differential Revision: https://phabricator.services.mozilla.com/D119694 --- caps/tests/unit/test_precursor_principal.js | 256 ++++++++++++++++++++ caps/tests/unit/xpcshell.ini | 3 + 2 files changed, 259 insertions(+) create mode 100644 caps/tests/unit/test_precursor_principal.js diff --git a/caps/tests/unit/test_precursor_principal.js b/caps/tests/unit/test_precursor_principal.js new file mode 100644 index 000000000000..9d1491b2e050 --- /dev/null +++ b/caps/tests/unit/test_precursor_principal.js @@ -0,0 +1,256 @@ +"use strict"; +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const { TestUtils } = ChromeUtils.import( + "resource://testing-common/TestUtils.jsm" +); +const { XPCShellContentUtils } = ChromeUtils.import( + "resource://testing-common/XPCShellContentUtils.jsm" +); +const { ExtensionTestUtils } = ChromeUtils.import( + "resource://testing-common/ExtensionXPCShellUtils.jsm" +); + +XPCShellContentUtils.init(this); +ExtensionTestUtils.init(this); + +const server = XPCShellContentUtils.createHttpServer({ + hosts: ["example.com", "example.org"], +}); + +server.registerPathHandler("/static_frames", (request, response) => { + response.setHeader("Content-Type", "text/html"); + response.write(` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + `); +}); + +server.registerPathHandler("/frame", (request, response) => { + response.setHeader("Content-Type", "text/html"); + response.write(`

HTTP Subframe

`); +}); + +server.registerPathHandler("/redirect", (request, response) => { + let redirect; + if (request.queryString == "com") { + redirect = "http://example.com/frame"; + } else if (request.queryString == "org") { + redirect = "http://example.org/frame"; + } else { + response.setStatusLine(request.httpVersion, 404, "Not found"); + return; + } + + response.setStatusLine(request.httpVersion, 302, "Found"); + response.setHeader("Location", redirect); +}); + +server.registerPathHandler("/client_replace", (request, response) => { + let redirect; + if (request.queryString == "blank") { + redirect = "about:blank"; + } else if (request.queryString == "data") { + redirect = "data:text/html,

Data Subframe

"; + } else { + response.setStatusLine(request.httpVersion, 404, "Not found"); + return; + } + + response.setHeader("Content-Type", "text/html"); + response.write(` + + `); +}); + +add_task(async function sandboxed_precursor() { + let extension = await ExtensionTestUtils.loadExtension({ + manifest: { + permissions: ["webRequest", "webRequestBlocking", ""], + }, + background() { + // eslint-disable-next-line no-undef + browser.webRequest.onBeforeRequest.addListener( + details => { + let url = new URL(details.url); + if (!url.pathname.includes("ext_redirect")) { + return {}; + } + + let redirectUrl; + if (url.search == "?com") { + redirectUrl = "http://example.com/frame"; + } else if (url.search == "?org") { + redirectUrl = "http://example.org/frame"; + } else if (url.search == "?data") { + redirectUrl = "data:text/html,

Data Subframe

"; + } + return { redirectUrl }; + }, + { urls: [""] }, + ["blocking"] + ); + }, + }); + await extension.startup(); + + registerCleanupFunction(async function() { + await extension.unload(); + }); + + for (let userContextId of [undefined, 1]) { + let comURI = Services.io.newURI("http://example.com"); + let comPrin = Services.scriptSecurityManager.createContentPrincipal( + comURI, + { userContextId } + ); + let orgURI = Services.io.newURI("http://example.org"); + let orgPrin = Services.scriptSecurityManager.createContentPrincipal( + orgURI, + { userContextId } + ); + + let page = await XPCShellContentUtils.loadContentPage( + "http://example.com/static_frames", + { + remote: true, + remoteSubframes: true, + userContextId, + } + ); + let bc = page.browsingContext; + + ok( + bc.currentWindowGlobal.documentPrincipal.equals(comPrin), + "toplevel principal matches" + ); + + // XXX: This is sketchy as heck, but it's also the easiest way to wait for + // the `window.location.replace` loads to finish. + await TestUtils.waitForCondition( + () => + bc.children.every( + child => + !child.currentWindowGlobal.documentURI.spec.includes( + "/client_replace" + ) + ), + "wait for every client_replace global to be replaced" + ); + + let principals = {}; + for (let child of bc.children) { + notEqual(child.name, "", "child frames must have names"); + ok(!(child.name in principals), "duplicate child frame name"); + principals[child.name] = child.currentWindowGlobal.documentPrincipal; + } + + function principal_is(name, expected) { + let principal = principals[name]; + info(`${name} = ${principal.origin}`); + ok(principal.equals(expected), `${name} is correct`); + } + function precursor_is(name, precursor) { + let principal = principals[name]; + info(`${name} = ${principal.origin}`); + ok(principal.isNullPrincipal, `${name} is null`); + ok( + principal.precursorPrincipal.equals(precursor), + `${name} has the correct precursor` + ); + } + + // Basic loads should have the principals or precursor principals for the + // document being loaded. + principal_is("same_origin", comPrin); + precursor_is("same_origin_sandbox", comPrin); + + principal_is("cross_origin", orgPrin); + precursor_is("cross_origin_sandbox", orgPrin); + + // Loads of a data: URI should complete with a sandboxed principal based on + // the principal which tried to perform the load. + precursor_is("data_uri", comPrin); + precursor_is("data_uri_sandbox", comPrin); + + // Loads which inherit principals, such as srcdoc an about:blank loads, + // should also inherit sandboxed precursor principals. + principal_is("srcdoc", comPrin); + precursor_is("srcdoc_sandbox", comPrin); + + principal_is("blank", comPrin); + precursor_is("blank_sandbox", comPrin); + + // Redirects shouldn't interfere with the final principal, and it should be + // based only on the final URI. + principal_is("redirect_com", comPrin); + precursor_is("redirect_com_sandbox", comPrin); + + principal_is("redirect_org", orgPrin); + precursor_is("redirect_org_sandbox", orgPrin); + + // Extension redirects should act like normal redirects, and still resolve + // with the principal or sandboxed principal of the final URI. + principal_is("ext_redirect_com_com", comPrin); + precursor_is("ext_redirect_com_com_sandbox", comPrin); + + principal_is("ext_redirect_com_org", orgPrin); + precursor_is("ext_redirect_com_org_sandbox", orgPrin); + + principal_is("ext_redirect_org_com", comPrin); + precursor_is("ext_redirect_org_com_sandbox", comPrin); + + principal_is("ext_redirect_org_org", orgPrin); + precursor_is("ext_redirect_org_org_sandbox", orgPrin); + + // When an extension redirects to a data: URI, we use the last non-data: URI + // in the chain as the precursor principal. + // FIXME: This should perhaps use the extension's principal instead? + precursor_is("ext_redirect_com_data", comPrin); + precursor_is("ext_redirect_com_data_sandbox", comPrin); + + precursor_is("ext_redirect_org_data", orgPrin); + precursor_is("ext_redirect_org_data_sandbox", orgPrin); + + // Check that navigations triggred by script within the frames will have the + // correct behaviour when navigating to blank and data URIs. + principal_is("client_replace_org_blank", orgPrin); + precursor_is("client_replace_org_blank_sandbox", orgPrin); + + precursor_is("client_replace_org_data", orgPrin); + precursor_is("client_replace_org_data_sandbox", orgPrin); + + await page.close(); + } +}); diff --git a/caps/tests/unit/xpcshell.ini b/caps/tests/unit/xpcshell.ini index fe6e9f0b3b45..9ff2b22617fc 100644 --- a/caps/tests/unit/xpcshell.ini +++ b/caps/tests/unit/xpcshell.ini @@ -6,3 +6,6 @@ head = [test_ipv6_host_literal.js] [test_oa_partitionKey_pattern.js] [test_site_origin.js] +[test_precursor_principal.js] +firefox-appdir = browser +skip-if = toolkit == 'android'