tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 985c5a4d6619cbdd890efbef9c7bb52a148c0cc1
parent 4ae3a234fbea4d3585c507d44bd4ffb6db5c001d
Author: Andrea Marchesini <amarchesini@mozilla.com>
Date:   Wed,  3 Dec 2025 21:12:48 +0000

Bug 1986320 - Implement an add-on block URL-Classifier feature - part 5 - Block top-level documents when loaded by add-ons, r=extension-reviewers,dimi,fluent-reviewers,robwu,bolsson,android-reviewers,android-l10n-reviewers,delphine,tcampbell

Differential Revision: https://phabricator.services.mozilla.com/D263425

Diffstat:
Mbrowser/base/content/blockedSite.js | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mbrowser/locales/en-US/browser/safebrowsing/blockedSite.ftl | 12++++++++++++
Mbrowser/locales/en-US/chrome/overrides/appstrings.properties | 1+
Mdocshell/base/nsDocShell.cpp | 16++++++++++++++++
Mdom/locales/en-US/chrome/appstrings.properties | 1+
Mmobile/android/android-components/components/browser/errorpages/src/main/java/mozilla/components/browser/errorpages/ErrorPages.kt | 4++++
Mmobile/android/android-components/components/browser/errorpages/src/main/res/values/strings.xml | 7+++++++
Mnetwerk/url-classifier/UrlClassifierCommon.cpp | 6++----
Mnetwerk/url-classifier/UrlClassifierCommon.h | 3+--
Mnetwerk/url-classifier/UrlClassifierFeatureHarmfulAddonProtection.cpp | 4----
Mtoolkit/components/extensions/test/browser/browser.toml | 2++
Atoolkit/components/extensions/test/browser/browser_ext_urlclassifier.js | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
12 files changed, 213 insertions(+), 14 deletions(-)

diff --git a/browser/base/content/blockedSite.js b/browser/base/content/blockedSite.js @@ -38,6 +38,19 @@ function getURL() { return url; } +function getAddonName() { + var url = document.documentURI; + var match = url.match(/&a=([^&]+)/); + + // match == null if not found; if so, return an empty string + // instead of what would turn out to be portions of the URI + if (!match) { + return ""; + } + + return decodeURIComponent(match[1]); +} + /** * Check whether this warning page is overridable or not, in which case * the "ignore the risk" suggestion in the error description @@ -72,6 +85,7 @@ function initPage() { deceptiveBlocked: "phishing", unwantedBlocked: "unwanted", harmfulBlocked: "harmful", + addonBlocked: "addon", }; const error = errorMap[getErrorCode()]; if (error === undefined) { @@ -108,6 +122,25 @@ function initPage() { innerDescNoOverride: "safeb-blocked-harmful-page-error-desc-no-override", learnMore: "safeb-blocked-harmful-page-learn-more", }, + addon: { + title: "safeb-blocked-addon-page-title", + shortDesc: "safeb-blocked-addon-page-short-desc", + innerDescOverride: [ + "safeb-blocked-addon-page-error-desc-override", + "safeb-blocked-addon-page-error-desc2-override", + "", + "safeb-blocked-addon-page-error-desc3-override", + "safeb-blocked-addon-page-error-desc4-override", + ], + innerDescNoOverride: [ + "safeb-blocked-addon-page-error-desc-override", + "safeb-blocked-addon-page-error-desc2-override", + "", + "safeb-blocked-addon-page-error-desc3-override", + "safeb-blocked-addon-page-error-desc4-override", + ], + learnMore: "safeb-blocked-addon-page-learn-more", + }, }; // Set page contents depending on type of blocked page @@ -126,14 +159,35 @@ function initPage() { } else { innerDescL10nID = messageIDs[error].innerDescOverride; } - if (error == "unwanted" || error == "harmful") { + if (error == "unwanted" || error == "harmful" || error == "addon") { document.getElementById("report_detection").remove(); } - // Add the inner description: - document.l10n.setAttributes(innerDesc, innerDescL10nID, { + const descArgs = { sitename: getHostString(), - }); + addonName: getAddonName(), + }; + + // Add the inner description: + if (Array.isArray(innerDescL10nID)) { + const template = innerDesc.cloneNode(true); + + while (innerDesc.firstChild) { + innerDesc.firstChild.remove(); + } + + for (const id of innerDescL10nID) { + if (id === "") { + innerDesc.appendChild(document.createElement("br")); + } + + const node = template.cloneNode(true); + document.l10n.setAttributes(node, id, descArgs); + innerDesc.appendChild(node); + } + } else { + document.l10n.setAttributes(innerDesc, innerDescL10nID, descArgs); + } // Add the learn more content: let learnMore = document.getElementById("learn_more"); diff --git a/browser/locales/en-US/browser/safebrowsing/blockedSite.ftl b/browser/locales/en-US/browser/safebrowsing/blockedSite.ftl @@ -6,10 +6,12 @@ safeb-blocked-phishing-page-title = Deceptive site ahead safeb-blocked-malware-page-title = Visiting this website may harm your computer safeb-blocked-unwanted-page-title = The site ahead may contain harmful programs safeb-blocked-harmful-page-title = The site ahead may contain malware +safeb-blocked-addon-page-title = Site blocked for your safety safeb-blocked-phishing-page-short-desc = { -brand-short-name } blocked this page because it may trick you into doing something dangerous like installing software or revealing personal information like passwords or credit cards. safeb-blocked-malware-page-short-desc = { -brand-short-name } blocked this page because it might attempt to install malicious software that may steal or delete personal information on your computer. safeb-blocked-unwanted-page-short-desc = { -brand-short-name } blocked this page because it might try to trick you into installing programs that harm your browsing experience (for example, by changing your homepage or showing extra ads on sites you visit). safeb-blocked-harmful-page-short-desc = { -brand-short-name } blocked this page because it might try to install dangerous apps that steal or delete your information (for example, photos, passwords, messages and credit cards). +safeb-blocked-addon-page-short-desc = { -brand-short-name } blocked this page because one of your add-ons tried to open it. This site could be used to steal your info — like passwords or credit card numbers. # Variables: # $advisoryname (string) - Name of the advisory entity safeb-palm-advisory-desc = Advisory provided by <a data-l10n-name='advisory_provider'>{ $advisoryname }</a>. @@ -34,3 +36,13 @@ safeb-blocked-unwanted-page-learn-more = Learn more about harmful and unwanted s safeb-blocked-harmful-page-error-desc-override = <span data-l10n-name='sitename'>{ $sitename }</span> has been <a data-l10n-name='error_desc_link'>reported as containing a potentially harmful application</a>. You can <a data-l10n-name='ignore_warning_link'>ignore the risk</a> and go to this unsafe site. safeb-blocked-harmful-page-error-desc-no-override = <span data-l10n-name='sitename'>{ $sitename }</span> has been <a data-l10n-name='error_desc_link'>reported as containing a potentially harmful application</a>. safeb-blocked-harmful-page-learn-more = Learn more about { -brand-short-name }’s Phishing and Malware Protection at <a data-l10n-name='firefox_support'>support.mozilla.org</a>. + +## Variables: +## $addonName (string) - the name of the harmful add-on +## $sitename (string) - Domain name for the blocked page + +safeb-blocked-addon-page-error-desc-override = <strong>Why was this site blocked?</strong> +safeb-blocked-addon-page-error-desc2-override = <strong>{ $sitename }</strong> may be linked to deceptive and harmful activity. +safeb-blocked-addon-page-error-desc3-override = <strong>What can you do about it?</strong> +safeb-blocked-addon-page-error-desc4-override = To stop this from happening again, you can remove or disable <strong>{ $addonName }</strong> from about:addons. +safeb-blocked-addon-page-learn-more = <a data-l10n-name='firefox_support'>Learn more about { -brand-short-name }’s Phishing and Malware Protection</a>. diff --git a/browser/locales/en-US/chrome/overrides/appstrings.properties b/browser/locales/en-US/chrome/overrides/appstrings.properties @@ -38,6 +38,7 @@ malwareBlocked=The site at %S has been reported as an attack site and has been b harmfulBlocked=The site at %S has been reported as a potentially harmful site and has been blocked based on your security preferences. unwantedBlocked=The site at %S has been reported as serving unwanted software and has been blocked based on your security preferences. deceptiveBlocked=This web page at %S has been reported as a deceptive site and has been blocked based on your security preferences. +addonBlocked=Site blocked for your safety. cspBlocked=This page has a content security policy that prevents it from being loaded in this way. xfoBlocked=This page has an X-Frame-Options policy that prevents it from being loaded in this context. corruptedContentErrorv2=The site at %S has experienced a network protocol violation that cannot be repaired. diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp @@ -3565,6 +3565,7 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI, } else if (NS_ERROR_PHISHING_URI == aError || NS_ERROR_MALWARE_URI == aError || NS_ERROR_UNWANTED_URI == aError || + NS_ERROR_HARMFULADDON_URI == aError || NS_ERROR_HARMFUL_URI == aError) { nsAutoCString host; aURI->GetHost(host); @@ -3587,6 +3588,8 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI, error = "unwantedBlocked"; } else if (NS_ERROR_HARMFUL_URI == aError) { error = "harmfulBlocked"; + } else if (NS_ERROR_HARMFULADDON_URI == aError) { + error = "addonBlocked"; } cssClass.AssignLiteral("blacklist"); @@ -3927,6 +3930,18 @@ nsresult nsDocShell::LoadErrorPage(nsIURI* aURI, const char16_t* aURL, errorPageUrl.AppendLiteral("&d="); errorPageUrl.AppendASCII(escapedDescription.get()); + nsCOMPtr<nsIWritablePropertyBag2> props(do_QueryInterface(aFailedChannel)); + if (props) { + nsAutoCString addonName; + props->GetPropertyAsACString(u"blockedExtension"_ns, addonName); + + nsCString escapedAddonName; + SAFE_ESCAPE(escapedAddonName, addonName, url_Path); + + errorPageUrl.AppendLiteral("&a="); + errorPageUrl.AppendASCII(escapedAddonName.get()); + } + nsCOMPtr<nsIURI> errorPageURI; nsresult rv = NS_NewURI(getter_AddRefs(errorPageURI), errorPageUrl); NS_ENSURE_SUCCESS(rv, rv); @@ -6343,6 +6358,7 @@ nsresult nsDocShell::FilterStatusForErrorPage( aStatus == NS_ERROR_PROXY_AUTHENTICATION_FAILED || aStatus == NS_ERROR_PROXY_TOO_MANY_REQUESTS || aStatus == NS_ERROR_MALFORMED_URI || + aStatus == NS_ERROR_HARMFULADDON_URI || aStatus == NS_ERROR_BLOCKED_BY_POLICY || aStatus == NS_ERROR_DOM_COOP_FAILED || aStatus == NS_ERROR_DOM_COEP_FAILED || diff --git a/dom/locales/en-US/chrome/appstrings.properties b/dom/locales/en-US/chrome/appstrings.properties @@ -31,6 +31,7 @@ malwareBlocked=The site at %S has been reported as an attack site and has been b harmfulBlocked=The site at %S has been reported as a potentially harmful site and has been blocked based on your security preferences. unwantedBlocked=The site at %S has been reported as serving unwanted software and has been blocked based on your security preferences. deceptiveBlocked=This web page at %S has been reported as a deceptive site and has been blocked based on your security preferences. +addonBlocked=Site blocked for your safety. cspBlocked=This page has a content security policy that prevents it from being loaded in this way. xfoBlocked=This page has an X-Frame-Options policy that prevents it from being loaded in this context. corruptedContentErrorv2=The site at %S has experienced a network protocol violation that cannot be repaired. diff --git a/mobile/android/android-components/components/browser/errorpages/src/main/java/mozilla/components/browser/errorpages/ErrorPages.kt b/mobile/android/android-components/components/browser/errorpages/src/main/java/mozilla/components/browser/errorpages/ErrorPages.kt @@ -240,6 +240,10 @@ enum class ErrorType( R.string.mozac_browser_errorpages_safe_phishing_uri_title, R.string.mozac_browser_errorpages_safe_phishing_uri_message, ), + NS_ERROR_HARMFULADDON_URI( + R.string.mozac_browser_errorpages_harmful_addon_uri_title, + R.string.mozac_browser_errorpages_harmful_addon_uri_message, + ), ERROR_HTTPS_ONLY( R.string.mozac_browser_errorpages_httpsonly_title, R.string.mozac_browser_errorpages_httpsonly_message, diff --git a/mobile/android/android-components/components/browser/errorpages/src/main/res/values/strings.xml b/mobile/android/android-components/components/browser/errorpages/src/main/res/values/strings.xml @@ -300,6 +300,13 @@ <p>This web page at %1$s has been reported as a deceptive site and has been blocked based on your security preferences.</p> ]]></string> + <!-- The document title and heading of an error page. --> + <string name="mozac_browser_errorpages_harmful_addon_uri_title">Site blocked for your safety</string> + <!-- The %1$s will be replaced by the malicious website URL--> + <string name="mozac_browser_errorpages_harmful_addon_uri_message"><![CDATA[ + <p>This web page at %1$s has been blocked because one of your add-ons tried to open it. This site could be used to steal your info — like passwords or credit card numbers.</p> + ]]></string> + <!-- The title of the error page for websites that do not support HTTPS when HTTPS-Only is turned on --> <string name="mozac_browser_errorpages_httpsonly_title">Secure Site Not Available</string> <!-- The text of the error page for websites that do not support HTTPS when HTTPS-Only is turned on. %1$s will be replaced with the URL of the website. --> diff --git a/netwerk/url-classifier/UrlClassifierCommon.cpp b/netwerk/url-classifier/UrlClassifierCommon.cpp @@ -57,8 +57,7 @@ bool UrlClassifierCommon::AddonMayLoad(nsIChannel* aChannel, nsIURI* aURI) { } /* static */ -bool UrlClassifierCommon::ShouldEnableProtectionForChannel( - nsIChannel* aChannel, bool aShouldAllowAddons) { +bool UrlClassifierCommon::ShouldEnableProtectionForChannel(nsIChannel* aChannel) { MOZ_ASSERT(aChannel); nsCOMPtr<nsIURI> chanURI; @@ -67,8 +66,7 @@ bool UrlClassifierCommon::ShouldEnableProtectionForChannel( return false; } - if (aShouldAllowAddons && - UrlClassifierCommon::AddonMayLoad(aChannel, chanURI)) { + if (UrlClassifierCommon::AddonMayLoad(aChannel, chanURI)) { return false; } diff --git a/netwerk/url-classifier/UrlClassifierCommon.h b/netwerk/url-classifier/UrlClassifierCommon.h @@ -39,8 +39,7 @@ class UrlClassifierCommon final { static bool AddonMayLoad(nsIChannel* aChannel, nsIURI* aURI); - static bool ShouldEnableProtectionForChannel(nsIChannel* aChannel, - bool aShouldAllowAddons = true); + static bool ShouldEnableProtectionForChannel(nsIChannel* aChannel); static nsresult SetBlockedContent(nsIChannel* channel, nsresult aErrorCode, const nsACString& aList, diff --git a/netwerk/url-classifier/UrlClassifierFeatureHarmfulAddonProtection.cpp b/netwerk/url-classifier/UrlClassifierFeatureHarmfulAddonProtection.cpp @@ -207,10 +207,6 @@ UrlClassifierFeatureHarmfulAddonProtection::MaybeCreate(nsIChannel* aChannel) { return nullptr; } - if (!UrlClassifierCommon::ShouldEnableProtectionForChannel(aChannel, false)) { - return nullptr; - } - // Recommended add-ons are exempt. extensions::WebExtensionPolicy* policy = GetAddonPolicy(aChannel); if (policy && policy->HasRecommendedState()) { diff --git a/toolkit/components/extensions/test/browser/browser.toml b/toolkit/components/extensions/test/browser/browser.toml @@ -104,6 +104,8 @@ support-files = ["!/toolkit/components/thumbnails/test/head.js"] ["browser_ext_trial_ml.js"] support-files = [ "!/toolkit/components/ml/tests/browser/head.js"] +["browser_ext_urlclassifier.js"] + ["browser_ext_webNavigation_eventpage.js"] ["browser_ext_webRequest_redirect_mozextension.js"] diff --git a/toolkit/components/extensions/test/browser/browser_ext_urlclassifier.js b/toolkit/components/extensions/test/browser/browser_ext_urlclassifier.js @@ -0,0 +1,109 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +const { AddonTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/AddonTestUtils.sys.mjs" +); + +AddonTestUtils.initMochitest(this); + +const server = AddonTestUtils.createHttpServer({ + hosts: ["expected.example.org", "extra.example.org"], +}); +server.registerPathHandler("/", (req, res) => { + info(`Test HTTP server for domain "${req.host}" got ${req.method} request\n`); + res.setStatusLine(req.httpVersion, 200, "OK"); + res.write("OK"); +}); + +add_task(async function test_extension_tab_create() { + Services.fog.testResetFOG(); + + await SpecialPowers.pushPrefEnv({ + set: [ + [ + "urlclassifier.features.harmfuladdon.blocklistHosts", + "expected.example.org", + ], + ], + }); + const id = "ext-create-tab@mochitest"; + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + browser_specific_settings: { + gecko: { id }, + }, + host_permissions: [ + "*://expected.example.org/*", + "*://extra.example.org/*", + ], + }, + background() { + const { browser } = this; + let tab; + browser.test.onMessage.addListener(async (msg, ...args) => { + if (msg === "create-tab") { + tab = await browser.tabs.create({ + url: "about:blank", + active: true, + }); + } else if (msg === "load-tab") { + await browser.tabs.update(tab.id, { url: args[0] }); + } else { + browser.test.fail(`Got unexpected test message ${msg}`); + } + browser.test.sendMessage(`${msg}:done`); + }); + }, + }); + + await extension.startup(); + + extension.sendMessage("create-tab"); + await extension.awaitMessage("create-tab:done"); + + const aboutBlockedLoaded = BrowserTestUtils.waitForContentEvent( + gBrowser.selectedTab.linkedBrowser, + "AboutBlockedLoaded", + true, + undefined, + true + ); + + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + extension.sendMessage("load-tab", "http://expected.example.org"); + await extension.awaitMessage("load-tab:done"); + + info("Wait for custom Glean ping submit"); + const gleanEvents = Glean.network.urlclassifierAddonBlock + .testGetValue() + ?.map(evt => evt.extra); + Assert.deepEqual( + gleanEvents, + [ + { + addon_id: id, + table: "harmfuladdon-blocklist-pref", + etld: "example.org", + }, + ], + "Got the expected Glean events" + ); + + await aboutBlockedLoaded; + + Services.fog.testResetFOG(); + + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + extension.sendMessage("load-tab", "http://extra.example.org"); + await extension.awaitMessage("load-tab:done"); + + const newGleanEvents = Glean.network.urlclassifierAddonBlock.testGetValue(); + Assert.deepEqual(newGleanEvents, null, "No glean event received"); + + await extension.unload(); + BrowserTestUtils.removeTab(gBrowser.selectedTab); +});