tor-browser

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

commit 4fd058453e5668dd6a39a8e6bf92e58fd11c6490
parent f142c9138f1d943190c5a8f5b4385ae86a6d0985
Author: Serban Stanca <sstanca@mozilla.com>
Date:   Sat, 20 Dec 2025 05:01:36 +0200

Revert "Bug 2005990 - Skip LNA checks when captive portal is active. r=necko-reviewers,valentin" for causing mochitests failures in browser_captivePortal_lna.js.

This reverts commit d5e7c19b494ed3306fb4e745597472f05165a663.

This reverts commit 9e69bd91cac88025a86f851be3eba8759d8a1a14.

This reverts commit 233b190d8229822d9532e2de6dae444527d4dd64.

Diffstat:
Mnetwerk/protocol/http/nsHttpChannel.cpp | 23-----------------------
Dnetwerk/test/unit/test_lna_captive_portal.js | 312-------------------------------------------------------------------------------
Mnetwerk/test/unit/test_local_network_access.js | 120+------------------------------------------------------------------------------
Mnetwerk/test/unit/xpcshell.toml | 2--
4 files changed, 1 insertion(+), 456 deletions(-)

diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp @@ -17,7 +17,6 @@ #include "mozilla/glean/AntitrackingMetrics.h" #include "mozilla/glean/NetwerkMetrics.h" #include "mozilla/glean/NetwerkProtocolHttpMetrics.h" -#include "mozilla/net/CaptivePortalService.h" #include "mozilla/net/CookieServiceParent.h" #include "mozilla/StoragePrincipalHelper.h" @@ -2087,28 +2086,6 @@ LNAPermission nsHttpChannel::UpdateLocalNetworkAccessPermissions( MOZ_ASSERT(mLoadInfo->TriggeringPrincipal(), "need triggering principal"); - // Skip LNA checks if the triggering principal and target are same origin - // Note: This could be a case where there is a network change or device - // migration to a private or corporate network - bool isSameOrigin = false; - nsresult rv = - mLoadInfo->TriggeringPrincipal()->IsSameOrigin(mURI, &isSameOrigin); - if (NS_SUCCEEDED(rv) && isSameOrigin) { - userPerms = LNAPermission::Granted; - return userPerms; - } - - // Skip LNA checks if captive portal is active - nsCOMPtr<nsICaptivePortalService> cps = CaptivePortalService::GetSingleton(); - if (cps) { - int32_t state = cps->State(); - if (state == nsICaptivePortalService::LOCKED_PORTAL && - aPermissionType == LOCAL_NETWORK_PERMISSION_KEY) { - userPerms = LNAPermission::Granted; - return userPerms; - } - } - // Step 1. Check for Existing Allow or Deny permission if (nsContentUtils::IsExactSitePermAllow(mLoadInfo->TriggeringPrincipal(), aPermissionType)) { diff --git a/netwerk/test/unit/test_lna_captive_portal.js b/netwerk/test/unit/test_lna_captive_portal.js @@ -1,312 +0,0 @@ -"use strict"; - -const { HttpServer } = ChromeUtils.importESModule( - "resource://testing-common/httpd.sys.mjs" -); - -let httpserver = null; -let lnaServer = null; - -ChromeUtils.defineLazyGetter(this, "cpURI", function () { - return ( - "http://localhost:" + httpserver.identity.primaryPort + "/captive.html" - ); -}); - -ChromeUtils.defineLazyGetter(this, "LNA_URL", function () { - return "http://localhost:" + lnaServer.identity.primaryPort + "/test"; -}); - -const SUCCESS_STRING = - '<meta http-equiv="refresh" content="0;url=https://support.mozilla.org/kb/captive-portal"/>'; -let cpResponse = SUCCESS_STRING; - -function captivePortalHandler(metadata, response) { - response.setHeader("Content-Type", "text/html"); - response.bodyOutputStream.write(cpResponse, cpResponse.length); -} - -function lnaHandler(metadata, response) { - response.setStatusLine(metadata.httpVersion, 200, "OK"); - let body = "success"; - response.bodyOutputStream.write(body, body.length); -} - -const PREF_CAPTIVE_ENABLED = "network.captive-portal-service.enabled"; -const PREF_CAPTIVE_TESTMODE = "network.captive-portal-service.testMode"; -const PREF_CAPTIVE_ENDPOINT = "captivedetect.canonicalURL"; -const PREF_CAPTIVE_MINTIME = "network.captive-portal-service.minInterval"; -const PREF_CAPTIVE_MAXTIME = "network.captive-portal-service.maxInterval"; -const PREF_DNS_NATIVE_IS_LOCALHOST = "network.dns.native-is-localhost"; - -const cps = Cc["@mozilla.org/network/captive-portal-service;1"].getService( - Ci.nsICaptivePortalService -); - -function makeChannel(url, triggeringPrincipalURI = null) { - let uri = NetUtil.newURI(url); - var principal = Services.scriptSecurityManager.createContentPrincipal( - uri, - {} - ); - - var triggeringPrincipal; - if (triggeringPrincipalURI) { - let triggeringURI = NetUtil.newURI(triggeringPrincipalURI); - triggeringPrincipal = Services.scriptSecurityManager.createContentPrincipal( - triggeringURI, - {} - ); - } else { - let triggeringURI = NetUtil.newURI("https://public.example.com"); - triggeringPrincipal = Services.scriptSecurityManager.createContentPrincipal( - triggeringURI, - {} - ); - } - - return NetUtil.newChannel({ - uri: url, - loadingPrincipal: principal, - triggeringPrincipal, - securityFlags: Ci.nsILoadInfo.SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT, - contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER, - }).QueryInterface(Ci.nsIHttpChannel); -} - -add_setup(async function () { - // Setup captive portal detection server - httpserver = new HttpServer(); - httpserver.registerPathHandler("/captive.html", captivePortalHandler); - httpserver.start(-1); - - // Setup LNA target server - lnaServer = new HttpServer(); - lnaServer.registerPathHandler("/test", lnaHandler); - lnaServer.start(-1); - - // Configure captive portal service - Services.prefs.setCharPref(PREF_CAPTIVE_ENDPOINT, cpURI); - Services.prefs.setIntPref(PREF_CAPTIVE_MINTIME, 50); - Services.prefs.setIntPref(PREF_CAPTIVE_MAXTIME, 100); - Services.prefs.setBoolPref(PREF_CAPTIVE_TESTMODE, true); - Services.prefs.setBoolPref(PREF_DNS_NATIVE_IS_LOCALHOST, true); - - // Configure LNA blocking - Services.prefs.setBoolPref("network.lna.blocking", true); - Services.prefs.setBoolPref("network.localhost.prompt.testing", true); - Services.prefs.setBoolPref("network.localnetwork.prompt.testing", true); - - registerCleanupFunction(async () => { - Services.prefs.clearUserPref(PREF_CAPTIVE_ENABLED); - Services.prefs.clearUserPref(PREF_CAPTIVE_TESTMODE); - Services.prefs.clearUserPref(PREF_CAPTIVE_ENDPOINT); - Services.prefs.clearUserPref(PREF_CAPTIVE_MINTIME); - Services.prefs.clearUserPref(PREF_CAPTIVE_MAXTIME); - Services.prefs.clearUserPref(PREF_DNS_NATIVE_IS_LOCALHOST); - Services.prefs.clearUserPref("network.lna.blocking"); - Services.prefs.clearUserPref("network.localhost.prompt.testing"); - Services.prefs.clearUserPref("network.localnetwork.prompt.testing"); - Services.prefs.clearUserPref("network.localhost.prompt.testing.allow"); - Services.prefs.clearUserPref("network.localnetwork.prompt.testing.allow"); - Services.prefs.clearUserPref("network.lna.address_space.private.override"); - - await new Promise(resolve => { - httpserver.stop(resolve); - }); - await new Promise(resolve => { - lnaServer.stop(resolve); - }); - }); -}); - -function observerPromise(topic) { - return new Promise(resolve => { - let observer = { - QueryInterface: ChromeUtils.generateQI(["nsIObserver"]), - observe(aSubject, aTopic, aData) { - if (aTopic == topic) { - Services.obs.removeObserver(observer, topic); - resolve(aData); - } - }, - }; - Services.obs.addObserver(observer, topic); - }); -} - -add_task(async function test_localnetwork_blocked_without_captive_portal() { - // Override address space to treat this localhost:port as Private (local network) - Services.prefs.setCharPref( - "network.lna.address_space.private.override", - "127.0.0.1:" + lnaServer.identity.primaryPort - ); - - Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, false); - Services.prefs.setBoolPref( - "network.localnetwork.prompt.testing.allow", - false - ); - - let chan = makeChannel(LNA_URL); - chan.loadInfo.parentIpAddressSpace = Ci.nsILoadInfo.Public; - - await new Promise(resolve => { - chan.asyncOpen(new ChannelListener(resolve, null, CL_EXPECT_FAILURE)); - }); - - Assert.equal( - chan.status, - Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED, - "Request should be blocked when captive portal is not active" - ); - Services.prefs.clearUserPref("network.lna.address_space.private.override"); -}); - -add_task(async function test_localnetwork_allowed_with_captive_portal() { - // Override address space to treat this localhost:port as Private (local network) - Services.prefs.setCharPref( - "network.lna.address_space.private.override", - "127.0.0.1:" + lnaServer.identity.primaryPort - ); - Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, false); - Assert.equal(cps.state, Ci.nsICaptivePortalService.UNKNOWN); - - // Start captive portal service and wait for it to detect "no captive portal" - let notification = observerPromise("network:captive-portal-connectivity"); - Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, true); - await notification; - Assert.equal(cps.state, Ci.nsICaptivePortalService.NOT_CAPTIVE); - - // Trigger captive portal detection (locked state) - cpResponse = "captive portal page"; - notification = observerPromise("captive-portal-login"); - cps.recheckCaptivePortal(); - await notification; - Assert.equal( - cps.state, - Ci.nsICaptivePortalService.LOCKED_PORTAL, - "Captive portal should be in LOCKED_PORTAL state" - ); - - // Set prompt to deny - but it should still succeed because captive portal is active - Services.prefs.setBoolPref( - "network.localnetwork.prompt.testing.allow", - false - ); - - let chan = makeChannel(LNA_URL); - chan.loadInfo.parentIpAddressSpace = Ci.nsILoadInfo.Public; - - await new Promise(resolve => { - chan.asyncOpen(new ChannelListener(resolve, null, 0)); - }); - - Assert.equal( - chan.status, - Cr.NS_OK, - "Request should succeed when captive portal is active (locked)" - ); - - // Cleanup: unlock the captive portal - cpResponse = SUCCESS_STRING; - notification = observerPromise("captive-portal-login-success"); - cps.recheckCaptivePortal(); - await notification; - Assert.equal(cps.state, Ci.nsICaptivePortalService.UNLOCKED_PORTAL); - - Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, false); - Services.prefs.clearUserPref("network.lna.address_space.private.override"); -}); - -add_task(async function test_localhost_blocked_during_captive_portal() { - Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, false); - Assert.equal(cps.state, Ci.nsICaptivePortalService.UNKNOWN); - - // Start captive portal service and wait for it to detect "no captive portal" - let notification = observerPromise("network:captive-portal-connectivity"); - Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, true); - await notification; - Assert.equal(cps.state, Ci.nsICaptivePortalService.NOT_CAPTIVE); - - // Trigger captive portal detection (locked state) - cpResponse = "captive portal page"; - notification = observerPromise("captive-portal-login"); - cps.recheckCaptivePortal(); - await notification; - Assert.equal( - cps.state, - Ci.nsICaptivePortalService.LOCKED_PORTAL, - "Captive portal should be in LOCKED_PORTAL state" - ); - - // Set prompt to deny localhost access - Services.prefs.setBoolPref("network.localhost.prompt.testing.allow", false); - - // Create a separate localhost server (without private override) - // This will be treated as Local address space, not Private - let localhostServer = new HttpServer(); - localhostServer.registerPathHandler("/test", lnaHandler); - localhostServer.start(-1); - - let localhostURL = - "http://localhost:" + localhostServer.identity.primaryPort + "/test"; - - let chan = makeChannel(localhostURL); - chan.loadInfo.parentIpAddressSpace = Ci.nsILoadInfo.Public; - - await new Promise(resolve => { - chan.asyncOpen(new ChannelListener(resolve, null, CL_EXPECT_FAILURE)); - }); - - Assert.equal( - chan.status, - Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED, - "Localhost access should be blocked even when captive portal is active" - ); - - // Cleanup - await new Promise(resolve => { - localhostServer.stop(resolve); - }); - - // Unlock the captive portal - cpResponse = SUCCESS_STRING; - notification = observerPromise("captive-portal-login-success"); - cps.recheckCaptivePortal(); - await notification; - Assert.equal(cps.state, Ci.nsICaptivePortalService.UNLOCKED_PORTAL); - - Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, false); -}); - -add_task( - async function test_localnetwork_blocked_after_captive_portal_unlocked() { - Services.prefs.setCharPref( - "network.lna.address_space.private.override", - "127.0.0.1:" + lnaServer.identity.primaryPort - ); - - Services.prefs.setBoolPref(PREF_CAPTIVE_ENABLED, false); - Services.prefs.setBoolPref( - "network.localnetwork.prompt.testing.allow", - false - ); - - Assert.equal(cps.state, Ci.nsICaptivePortalService.UNKNOWN); - - let chan = makeChannel(LNA_URL); - chan.loadInfo.parentIpAddressSpace = Ci.nsILoadInfo.Public; - - await new Promise(resolve => { - chan.asyncOpen(new ChannelListener(resolve, null, CL_EXPECT_FAILURE)); - }); - - Assert.equal( - chan.status, - Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED, - "Request should be blocked again when captive portal is no longer active" - ); - Services.prefs.clearUserPref("network.lna.address_space.private.override"); - } -); diff --git a/netwerk/test/unit/test_local_network_access.js b/netwerk/test/unit/test_local_network_access.js @@ -11,7 +11,7 @@ const override = Cc["@mozilla.org/network/native-dns-override;1"].getService( Ci.nsINativeDNSResolverOverride ); -function makeChannel(url, triggeringPrincipalURI = null) { +function makeChannel(url) { let uri2 = NetUtil.newURI(url); // by default system principal is used, which cannot be used for permission based tests // because the default system principal has all permissions @@ -19,29 +19,9 @@ function makeChannel(url, triggeringPrincipalURI = null) { uri2, {} ); - - // For LNA tests, we need a cross-origin triggering principal to test blocking behavior - // If not specified, use a different origin to ensure cross-origin requests - var triggeringPrincipal; - if (triggeringPrincipalURI) { - let triggeringURI = NetUtil.newURI(triggeringPrincipalURI); - triggeringPrincipal = Services.scriptSecurityManager.createContentPrincipal( - triggeringURI, - {} - ); - } else { - // Default to a cross-origin principal (public.example.com) - let triggeringURI = NetUtil.newURI("https://public.example.com"); - triggeringPrincipal = Services.scriptSecurityManager.createContentPrincipal( - triggeringURI, - {} - ); - } - return NetUtil.newChannel({ uri: url, loadingPrincipal: principal, - triggeringPrincipal, securityFlags: Ci.nsILoadInfo.SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT, contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER, }).QueryInterface(Ci.nsIHttpChannel); @@ -700,101 +680,3 @@ add_task(async function lna_local_network_to_localhost_skip_checks() { "network.lna.local-network-to-localhost.skip-checks" ); }); - -// Test that same-origin requests skip LNA checks -add_task(async function lna_same_origin_skip_checks() { - // Ensure the local-network-to-localhost skip pref is disabled for this test - Services.prefs.setBoolPref( - "network.lna.local-network-to-localhost.skip-checks", - false - ); - - // Test cases: [triggeringOriginURI, targetURL, parentSpace, expectedStatus, description] - const sameOriginTestCases = [ - // Same origin cases - should skip LNA checks and allow the request - [ - H1_URL, - H1_URL + "/test_lna", - Ci.nsILoadInfo.Public, - Cr.NS_OK, - "same origin localhost to localhost from Public should be allowed", - ], - [ - H1_URL, - H1_URL + "/test_lna", - Ci.nsILoadInfo.Private, - Cr.NS_OK, - "same origin localhost to localhost from Private should be allowed", - ], - [ - H2_URL, - H2_URL + "/test_lna", - Ci.nsILoadInfo.Public, - Cr.NS_OK, - "same origin localhost to localhost (H2) from Public should be allowed", - ], - [ - H2_URL, - H2_URL + "/test_lna", - Ci.nsILoadInfo.Private, - Cr.NS_OK, - "same origin localhost to localhost (H2) from Private should be allowed", - ], - - // Cross-origin cases - should apply normal LNA checks and block - // Use null to get the default cross-origin principal (public.example.com) - [ - null, - H1_URL + "/test_lna", - Ci.nsILoadInfo.Public, - Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED, - "cross origin to localhost from Public should be blocked", - ], - // Note: Private->Local transition test removed temporarily - // as there may be other logic affecting this transition - - // Same origin but from local address space should still be allowed - [ - H1_URL, - H1_URL + "/test_lna", - Ci.nsILoadInfo.Local, - Cr.NS_OK, - "same origin localhost to localhost from Local should be allowed", - ], - ]; - - for (let [ - triggeringOriginURI, - targetURL, - parentSpace, - expectedStatus, - description, - ] of sameOriginTestCases) { - info(`Testing same origin check: ${description}`); - - // Disable prompt simulation for clean testing - Services.prefs.setBoolPref("network.localhost.prompt.testing.allow", false); - - // Use makeChannel with explicit triggering principal - let chan = makeChannel(targetURL, triggeringOriginURI); - chan.loadInfo.parentIpAddressSpace = parentSpace; - - let expectFailure = expectedStatus !== Cr.NS_OK ? CL_EXPECT_FAILURE : 0; - - await new Promise(resolve => { - chan.asyncOpen(new ChannelListener(resolve, null, expectFailure)); - }); - - Assert.equal( - chan.status, - expectedStatus, - `Status should match for: ${description}` - ); - if (expectedStatus === Cr.NS_OK) { - Assert.equal( - chan.protocolVersion, - targetURL.startsWith(H2_URL) ? "h2" : "http/1.1" - ); - } - } -}); diff --git a/netwerk/test/unit/xpcshell.toml b/netwerk/test/unit/xpcshell.toml @@ -1115,8 +1115,6 @@ skip-if = [ ["test_local_network_access.js"] -["test_lna_captive_portal.js"] - ["test_localhost_offline.js"] ["test_localstreams.js"]