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:
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"]