Firefox · Toolkit
CVE-2026-6782
Logic Error in Toolkit
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
inittoolkit/components/ipprotection/IPPSessionPrefManager.sys.mjs |
modified | |
initOnStartupCompletedtoolkit/components/ipprotection/IPPSessionPrefManager.sys.mjs |
modified | |
uninittoolkit/components/ipprotection/IPPSessionPrefManager.sys.mjs |
modified | |
iftoolkit/components/ipprotection/IPPSessionPrefManager.sys.mjs |
modified | |
starttoolkit/components/ipprotection/IPPSessionPrefManager.sys.mjs |
modified | |
fortoolkit/components/ipprotection/IPPSessionPrefManager.sys.mjs |
modified | |
stoptoolkit/components/ipprotection/IPPSessionPrefManager.sys.mjs |
modified | |
add_setuptoolkit/components/ipprotection/tests/xpcshell/test_IPPSessionPrefManager.js |
modified | |
add_tasktoolkit/components/ipprotection/tests/xpcshell/test_IPPSessionPrefManager.js |
modified |
Files Changed
toolkit/components/ipprotection/IPPSessionPrefManager.sys.mjstoolkit/components/ipprotection/IPProtectionActivator.sys.mjstoolkit/components/ipprotection/docs/Components.rsttoolkit/components/ipprotection/moz.buildtoolkit/components/ipprotection/tests/xpcshell/test_IPPSessionPrefManager.jstoolkit/components/ipprotection/tests/xpcshell/xpcshell.toml
Patch
diff --git a/toolkit/components/ipprotection/IPPSessionPrefManager.sys.mjs b/toolkit/components/ipprotection/IPPSessionPrefManager.sys.mjs
new file mode 100644
index 00000000000..b5a7994e413
--- /dev/null
+++ b/toolkit/components/ipprotection/IPPSessionPrefManager.sys.mjs
@@ -0,0 +1,107 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * @typedef {object} LazyModules
+ * @property {import("./IPPProxyManager.sys.mjs").IPPProxyManager} IPPProxyManager
+ * // ProxyManager
+ * @property {import("./IPPProxyManager.sys.mjs").IPPProxyStates} IPPProxyStates
+ * // Proxy States
+ * @property {import("../../modules/Preferences.sys.mjs").Preferences} Preferences
+ * // Pref Service
+ */
+
+/** @type {LazyModules} */
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ IPPProxyStates:
+ "moz-src:///toolkit/components/ipprotection/IPPProxyManager.sys.mjs",
+ IPPProxyManager:
+ "moz-src:///toolkit/components/ipprotection/IPPProxyManager.sys.mjs",
+ Preferences: "resource://gre/modules/Preferences.sys.mjs",
+});
+
+/**
+ * This class monitors the proxy state.
+ * When the proxy becomes active it will set prefs temporarily for the session
+ * and resets them when the proxy is no longer active.
+ */
+export class IPPSessionPrefManagerClass {
+ #active = false;
+ /** @type {Map<string, Function>} */
+ #changedPrefs = new Map();
+ #observedPrefs;
+
+ /**
+ * Get the list of prefs that should be set when the proxy is active.
+ */
+ static getPrefs() {
+ return [["media.peerconnection.ice.proxy_only_if_behind_proxy", true]];
+ }
+ init() {}
+
+ initOnStartupCompleted() {
+ lazy.IPPProxyManager.addEventListener(
+ "IPPProxyManager:StateChanged",
+ this.#handleStateChange
+ );
+ }
+
+ uninit() {
+ lazy.IPPProxyManager.removeEventListener(
+ "IPPProxyManager:StateChanged",
+ this.#handleStateChange
+ );
+ this.stop();
+ }
+
+ #handleStateChange = () => {
+ if (lazy.IPPProxyManager.state === lazy.IPPProxyStates.ACTIVE) {
+ this.start();
+ return;
+ }
+ this.stop();
+ };
+
+ start() {
+ if (this.#active) {
+ return;
+ }
+ this.#active = true;
+ for (let [prefName, prefValue] of this.#observedPrefs) {
+ // Do not change user prefs.
+ if (lazy.Preferences.isSet(prefName)) {
+ continue;
+ }
+ lazy.Preferences.set(prefName, prefValue);
+ // If the user changes the pref, while we have changed it
+ // keep the user change, and abort the reset.
+ const callback = () => {
+ this.#changedPrefs.delete(prefName);
+ lazy.Preferences.ignore(prefName, callback);
+ };
+ this.#changedPrefs.set(prefName, callback);
+ lazy.Preferences.observe(prefName, callback);
+ }
+ }
+ stop() {
+ if (!this.#active) {
+ return;
+ }
+ this.#active = false;
+
+ for (const [pref, callback] of this.#changedPrefs) {
+ lazy.Preferences.reset(pref);
+ lazy.Preferences.ignore(pref, callback);
+ }
+ this.#changedPrefs = new Map();
+ }
+
+ constructor(observedPrefs = IPPSessionPrefManagerClass.getPrefs()) {
+ this.#observedPrefs = observedPrefs;
+ }
+}
+
+export const IPPSessionPrefManager = new IPPSessionPrefManagerClass();
diff --git a/toolkit/components/ipprotection/IPProtectionActivator.sys.mjs b/toolkit/components/ipprotection/IPProtectionActivator.sys.mjs
index 582224093f4..84a269e482e 100644
--- a/toolkit/components/ipprotection/IPProtectionActivator.sys.mjs
+++ b/toolkit/components/ipprotection/IPProtectionActivator.sys.mjs
@@ -9,11 +9,13 @@ import { IPPAutoStartHelpers } from "moz-src:///toolkit/components/ipprotection/
import { IPPNimbusHelper } from "moz-src:///toolkit/components/ipprotection/IPPNimbusHelper.sys.mjs";
import { IPProtectionServerlist } from "moz-src:///toolkit/components/ipprotection/IPProtectionServerlist.sys.mjs";
import { IPPStartupCache } from "moz-src:///toolkit/components/ipprotection/IPPStartupCache.sys.mjs";
+import { IPPSessionPrefManager } from "moz-src:///toolkit/components/ipprotection/IPPSessionPrefManager.sys.mjs";
const coreHelpers = [
IPPStartupCache,
IPProtectionServerlist,
IPPProxyManager,
+ IPPSessionPrefManager,
IPPAutoRestoreHelper,
...IPPAutoStartHelpers,
IPPNimbusHelper,
diff --git a/toolkit/components/ipprotection/docs/Components.rst b/toolkit/components/ipprotection/docs/Components.rst
index 240a963c97c..2c6a290e720 100644
--- a/toolkit/components/ipprotection/docs/Components.rst
+++ b/toolkit/components/ipprotection/docs/Components.rst
@@ -48,6 +48,7 @@ A diagram of all the main components is the following:
IPPAutoStart["Auto-Start Helper"]
IPPAutoRestoreHelper["Auto-Restore Helper"]
IPPNimbusHelper["Nimbus Eligibility Helper"]
+ IPPSessionPrefManager["Session Pref Manager"]
IPPExceptionsManager
end
@@ -182,6 +183,11 @@ IPPEnrollAndEntitleManager
Orchestrates the FxA-based enrollment flow with Guardian and updates the
service when enrollment or entitlement status changes.
+IPPSessionPrefManager
+ Sets session-scoped preferences while the
+ proxy is active and resets them when it deactivates, preserving any
+ user-set values.
+
Browser components (``browser/components/ipprotection``)
---------------------------------------------------------
diff --git a/toolkit/components/ipprotection/moz.build b/toolkit/components/ipprotection/moz.build
index 640ac1d27b9..f335716c7ce 100644
--- a/toolkit/components/ipprotection/moz.build
+++ b/toolkit/components/ipprotection/moz.build
@@ -24,6 +24,7 @@ MOZ_SRC_FILES += [
"IPProtectionActivator.sys.mjs",
"IPProtectionServerlist.sys.mjs",
"IPProtectionService.sys.mjs",
+ "IPPSessionPrefManager.sys.mjs",
"IPPStartupCache.sys.mjs",
]
diff --git a/toolkit/components/ipprotection/tests/xpcshell/test_IPPSessionPrefManager.js b/toolkit/components/ipprotection/tests/xpcshell/test_IPPSessionPrefManager.js
new file mode 100644
index 00000000000..d42ec12773d
--- /dev/null
+++ b/toolkit/components/ipprotection/tests/xpcshell/test_IPPSessionPrefManager.js
@@ -0,0 +1,100 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { IPPSessionPrefManagerClass } = ChromeUtils.importESModule(
+ "moz-src:///toolkit/components/ipprotection/IPPSessionPrefManager.sys.mjs"
+);
+
+const TEST_PREF = "browser.ipProtection.guardian.endpoint";
+const TEST_VALUE = "https://session-pref-manager.example.com/";
+
+add_setup(function () {
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref(TEST_PREF);
+ });
+});
+
+/**
+ * start() sets the pref; stop() resets it back.
+ */
+add_task(function test_start_sets_stop_resets() {
+ Services.prefs.clearUserPref(TEST_PREF);
+
+ let manager = new IPPSessionPrefManagerClass([[TEST_PREF, TEST_VALUE]]);
+ manager.start();
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/toolkit/components/ipprotection/tests/xpcshell/test_IPPSessionPrefManager.js b/toolkit/components/ipprotection/tests/xpcshell/test_IPPSessionPrefManager.js
new file mode 100644
index 00000000000..d42ec12773d
--- /dev/null
+++ b/toolkit/components/ipprotection/tests/xpcshell/test_IPPSessionPrefManager.js
@@ -0,0 +1,100 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { IPPSessionPrefManagerClass } = ChromeUtils.importESModule(
+ "moz-src:///toolkit/components/ipprotection/IPPSessionPrefManager.sys.mjs"
+);
+
+const TEST_PREF = "browser.ipProtection.guardian.endpoint";
+const TEST_VALUE = "https://session-pref-manager.example.com/";
+
+add_setup(function () {
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref(TEST_PREF);
+ });
+});
+
+/**
+ * start() sets the pref; stop() resets it back.
+ */
+add_task(function test_start_sets_stop_resets() {
+ Services.prefs.clearUserPref(TEST_PREF);
+
+ let manager = new IPPSessionPrefManagerClass([[TEST_PREF, TEST_VALUE]]);
+ manager.start();
+
+ Assert.equal(
+ Services.prefs.getCharPref(TEST_PREF, ""),
+ TEST_VALUE,
+ "start() should set the managed pref"
+ );
+
+ manager.stop();
+
+ Assert.ok(
+ !Services.prefs.prefHasUserValue(TEST_PREF),
+ "stop() should clear the managed pref"
+ );
+});
+
+/**
+ * When the pref already has a user value, start() must not overwrite it and
+ * stop() must not clear it.
+ */
+add_task(function test_user_set_pref_is_not_touched() {
+ const USER_VALUE = "https://user-set.example.com/";
+ Services.prefs.setCharPref(TEST_PREF, USER_VALUE);
+
+ let manager = new IPPSessionPrefManagerClass([[TEST_PREF, TEST_VALUE]]);
+ manager.start();
+
+ Assert.equal(
+ Services.prefs.getCharPref(TEST_PREF, ""),
+ USER_VALUE,
+ "start() should not overwrite a user-set pref"
+ );
+
+ manager.stop();
+
+ Assert.equal(
+ Services.prefs.getCharPref(TEST_PREF, ""),
+ USER_VALUE,
+ "stop() should not clear a pref it did not change"
+ );
+
+ Services.prefs.clearUserPref(TEST_PREF);
+});
+
+/**
+ * When start() changes the pref and the user subsequently changes it again,
+ * stop() must leave the user's value in place.
+ */
+add_task(function test_user_change_after_start_prevents_reset() {
+ Services.prefs.clearUserPref(TEST_PREF);
+
+ let manager = new IPPSessionPrefManagerClass([[TEST_PREF, TEST_VALUE]]);
+ manager.start();
+
+ Assert.equal(
+ Services.prefs.getCharPref(TEST_PREF, ""),
+ TEST_VALUE,
+ "start() should set the managed pref"
+ );
+
+ // Simulate the user changing the pref (e.g. via about:config).
+ const USER_CHANGED_VALUE = "https://user-changed.example.com/";
+ Services.prefs.setCharPref(TEST_PREF, USER_CHANGED_VALUE);
+
+ manager.stop();
+
+ Assert.equal(
+ Services.prefs.getCharPref(TEST_PREF, ""),
+ USER_CHANGED_VALUE,
+ "stop() should not reset a pref the user subsequently changed"
+ );
+
+ Services.prefs.clearUserPref(TEST_PREF);
+});
diff --git a/toolkit/components/ipprotection/tests/xpcshell/xpcshell.toml b/toolkit/components/ipprotection/tests/xpcshell/xpcshell.toml
index 3a209d15bec..0bba9d71b78 100644
--- a/toolkit/components/ipprotection/tests/xpcshell/xpcshell.toml
+++ b/toolkit/components/ipprotection/tests/xpcshell/xpcshell.toml
@@ -17,6 +17,8 @@ prefs = [
["test_IPPExceptionsManager.js"]
+["test_IPPSessionPrefManager.js"]
+
["test_IPPStartupCache.js"]
["test_IPProtectionServerlist.js"]
Loading diff…
References
On This Page