diff options
Diffstat (limited to 'toolkit/components/extensions/test/xpcshell/test_ext_dnr_system_restrictions.js')
-rw-r--r-- | toolkit/components/extensions/test/xpcshell/test_ext_dnr_system_restrictions.js | 283 |
1 files changed, 283 insertions, 0 deletions
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_dnr_system_restrictions.js b/toolkit/components/extensions/test/xpcshell/test_ext_dnr_system_restrictions.js new file mode 100644 index 0000000000..84464d0cba --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_dnr_system_restrictions.js @@ -0,0 +1,283 @@ +"use strict"; + +const server = createHttpServer({ hosts: ["example.com", "restricted"] }); +server.registerPathHandler("/", (req, res) => { + res.setHeader("Access-Control-Allow-Origin", "*"); + res.write("response from server"); +}); +server.registerPathHandler("/style_with_import.css", (req, res) => { + res.setHeader("Content-Type", "text/css"); + res.setHeader("Access-Control-Allow-Origin", "*"); + res.write("@import url('http://example.com/imported.css');"); +}); +server.registerPathHandler("/imported.css", (req, res) => { + res.setHeader("Content-Type", "text/css"); + res.setHeader("Access-Control-Allow-Origin", "*"); + res.write("imported_stylesheet_here { }"); +}); + +add_setup(() => { + Services.prefs.setBoolPref("extensions.manifestV3.enabled", true); + Services.prefs.setBoolPref("extensions.dnr.enabled", true); + // The restrictedDomains pref should be set early, because the pref is read + // only once (on first use) by WebExtensionPolicy::IsRestrictedURI. + Services.prefs.setCharPref( + "extensions.webextensions.restrictedDomains", + "restricted" + ); +}); + +async function startDNRExtension() { + let extension = ExtensionTestUtils.loadExtension({ + async background() { + await browser.declarativeNetRequest.updateSessionRules({ + addRules: [ + { + id: 1, + condition: { resourceTypes: ["xmlhttprequest", "stylesheet"] }, + action: { type: "block" }, + }, + { + id: 2, + condition: { urlFilter: "blockme", resourceTypes: ["main_frame"] }, + action: { type: "block" }, + }, + ], + }); + browser.test.sendMessage("dnr_registered"); + }, + manifest: { + manifest_version: 3, + permissions: ["declarativeNetRequest"], + }, + }); + await extension.startup(); + await extension.awaitMessage("dnr_registered"); + return extension; +} + +add_task(async function dnr_ignores_system_requests() { + let extension = await startDNRExtension(); + Assert.equal( + await (await fetch("http://example.com/")).text(), + "response from server", + "DNR should not block requests from system principal" + ); + await extension.unload(); +}); + +add_task(async function dnr_ignores_requests_to_restrictedDomains() { + let extension = await startDNRExtension(); + Assert.equal( + await ExtensionTestUtils.fetch("http://example.com/", "http://restricted/"), + "response from server", + "DNR should not block destination in restrictedDomains" + ); + await extension.unload(); +}); + +add_task(async function dnr_ignores_initiator_from_restrictedDomains() { + let extension = await startDNRExtension(); + Assert.equal( + await ExtensionTestUtils.fetch("http://restricted/", "http://example.com/"), + "response from server", + "DNR should not block requests initiated from a page in restrictedDomains" + ); + await extension.unload(); +}); + +add_task(async function dnr_ignores_navigation_to_restrictedDomains() { + let extension = await startDNRExtension(); + let contentPage = await ExtensionTestUtils.loadContentPage( + "http://restricted/?blockme" + ); + await contentPage.spawn([], () => { + const { document } = content; + Assert.equal(document.URL, "http://restricted/?blockme", "Same URL"); + Assert.equal(document.body.textContent, "response from server", "body"); + }); + await contentPage.close(); + await extension.unload(); +}); + +add_task(async function dnr_ignores_css_import_at_restrictedDomains() { + // CSS @import have triggeringPrincipal set to the URL of the stylesheet, + // and the loadingPrincipal set to the web page. To verify that access is + // indeed being restricted as expected, confirm that none of the stylesheet + // requests are blocked by the DNR extension. + let extension = await startDNRExtension(); + let contentPage = await ExtensionTestUtils.loadContentPage( + "http://restricted/" + ); + await contentPage.spawn([], async () => { + // Use wrappedJSObject so that all operations below are with the principal + // of the content instead of the system principal (from this ContentTask). + const { document } = content.wrappedJSObject; + const style = document.createElement("link"); + style.rel = "stylesheet"; + // Note: intentionally not at "http://restricted/" because we want to check + // that subresources from a restricted domain are ignored by DNR.. + style.href = "http://example.com/style_with_import.css"; + style.crossOrigin = "anonymous"; + await new Promise(resolve => { + info("Waiting for style sheet to load..."); + style.onload = resolve; + document.head.append(style); + }); + const importRule = style.sheet.cssRules[0]; + Assert.equal( + importRule?.cssText, + `@import url("http://example.com/imported.css");`, + "Not blocked by DNR: Loaded style_with_import.css" + ); + // Waiving Xrays here because we cannot read cssRules despite CORS because + // that is not implemented for child stylesheets (loaded via @import): + // https://searchfox.org/mozilla-central/rev/55d5c4b9dffe5e59eb6b019c1a930ec9ada47e10/layout/style/Loader.cpp#2052 + const importedStylesheet = Cu.unwaiveXrays(importRule.styleSheet); + Assert.equal( + importedStylesheet.cssRules[0]?.cssText, + "imported_stylesheet_here { }", + "Not blocked by DNR: Loaded import.css" + ); + }); + await contentPage.close(); + await extension.unload(); +}); + +add_task( + { pref_set: [["extensions.dnr.feedback", true]] }, + async function testMatchOutcome_and_restrictedDomains() { + let extension = ExtensionTestUtils.loadExtension({ + async background() { + await browser.declarativeNetRequest.updateSessionRules({ + addRules: [{ id: 1, condition: {}, action: { type: "block" } }], + }); + const type = "other"; // matches the condition of the above rule. + + browser.test.assertDeepEq( + { matchedRules: [] }, + await browser.declarativeNetRequest.testMatchOutcome({ + url: "http://restricted/", + type, + }), + "testMatchOutcome ignores restricted url" + ); + browser.test.assertDeepEq( + { matchedRules: [] }, + await browser.declarativeNetRequest.testMatchOutcome({ + url: "http://example.com/", + initiator: "http://restricted/", + type, + }), + "testMatchOutcome ignores restricted initiator" + ); + browser.test.sendMessage("done"); + }, + manifest: { + manifest_version: 3, + permissions: ["declarativeNetRequest", "declarativeNetRequestFeedback"], + }, + }); + await extension.startup(); + await extension.awaitMessage("done"); + await extension.unload(); + } +); + +add_task( + // In debug builds, any attempt to load data:-URLs in the parent process + // results in a crash or at least a logged error, via + // nsContentSecurityUtils::ValidateScriptFilename. + // + // Xpcshell tests use loadFrameScript with data:-URLs, which could trigger the + // above error / crash, when a page is loaded in the parent process. + // For example, the following error message (or crash), + // "InternalError: unsafe filename: data:text/javascript,//" + // "Hit MOZ_CRASH(Blocking a script load data:text/javascript,// from file (None))" + // is triggered because of the loadFrameScript call at + // https://searchfox.org/mozilla-central/rev/11dbac7f64f509b78037465cbb4427ed71f8b565/testing/modules/XPCShellContentUtils.sys.mjs#308 + // + // This test loads about:logo in the parent, because nsAboutRedirector.cpp + // registers about:logo without nsIAboutModule::URI_MUST_LOAD_IN_CHILD. + // When about:logo is loaded, the ContentPage test helper also triggers the + // above error/crash at: + // https://searchfox.org/mozilla-central/rev/11dbac7f64f509b78037465cbb4427ed71f8b565/testing/modules/XPCShellContentUtils.sys.mjs#224,242 + // + // Opt out of the check/crash from ValidateScriptFilename: + { pref_set: [["security.allow_parent_unrestricted_js_loads", true]] }, + async function non_system_request_with_disallowed_scheme() { + let extension = await startDNRExtension(); + Assert.equal( + await (await fetch("http://example.com/")).text(), + "response from server", + "DNR should not block requests from system principal" + ); + // We are loading about:logo for the following reasons: + // - It is a regular content principal, NOT a system principal. + // - It is an about:-URL that resolves across all builds (part of toolkit/). + // - It does not have a CSP (intentional - bug 1587417). That enables us to + // send a fetch() request below. + let contentPage = await ExtensionTestUtils.loadContentPage( + "about:logo?blockme" + ); + await contentPage.spawn([], async () => { + const { document } = content; + // To make sure that the test does not pass trivially, we verify that it + // is not the system principal (because dnr_ignores_system_requests + // already tests that) and not a null principal (because that translates + // to a void "initiator" in the DNR API, which would pass access checks). + Assert.ok( + document.nodePrincipal.isContentPrincipal, + "about:logo has content principal (not system or NullPrincipal))" + ); + Assert.equal(document.URL, "about:logo?blockme", "Same URL"); + Assert.equal( + await (await content.fetch("http://example.com/")).text(), + "response from server", + "fetch() at about:logo not blocked by DNR" + ); + }); + await contentPage.close(); + await extension.unload(); + } +); + +add_task( + { pref_set: [["extensions.dnr.feedback", true]] }, + async function testMatchOutcome_non_system_request_with_disallowed_scheme() { + let extension = ExtensionTestUtils.loadExtension({ + async background() { + await browser.declarativeNetRequest.updateSessionRules({ + addRules: [{ id: 1, condition: {}, action: { type: "block" } }], + }); + const type = "other"; // matches the condition of the above rule. + + browser.test.assertDeepEq( + { matchedRules: [] }, + await browser.declarativeNetRequest.testMatchOutcome({ + url: "about:logo", + type, + }), + "testMatchOutcome ignores url with disallowed schema" + ); + browser.test.assertDeepEq( + { matchedRules: [] }, + await browser.declarativeNetRequest.testMatchOutcome({ + url: "http://example.com/", + initiator: "about:logo", + type, + }), + "testMatchOutcome ignores initiator with disallowed schema" + ); + browser.test.sendMessage("done"); + }, + manifest: { + manifest_version: 3, + permissions: ["declarativeNetRequest", "declarativeNetRequestFeedback"], + }, + }); + await extension.startup(); + await extension.awaitMessage("done"); + await extension.unload(); + } +); |