Low firefox Logic Error 🔧 Commit mapped

Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactlow
DescriptionInformation disclosure in the IP Protection component
ComponentToolkit
Bug ClassLogic Error
Tracker2026571
Fix commit3c79b69e2fe1 (firefox) +218/-0
CISA KEVNot listed
CreditedYuki Umemura
Disclosed2026-04-21

Changed Functions

FunctionChangeNotes
init
toolkit/components/ipprotection/IPPSessionPrefManager.sys.mjs
modified
initOnStartupCompleted
toolkit/components/ipprotection/IPPSessionPrefManager.sys.mjs
modified
uninit
toolkit/components/ipprotection/IPPSessionPrefManager.sys.mjs
modified
if
toolkit/components/ipprotection/IPPSessionPrefManager.sys.mjs
modified
start
toolkit/components/ipprotection/IPPSessionPrefManager.sys.mjs
modified
for
toolkit/components/ipprotection/IPPSessionPrefManager.sys.mjs
modified
stop
toolkit/components/ipprotection/IPPSessionPrefManager.sys.mjs
modified
add_setup
toolkit/components/ipprotection/tests/xpcshell/test_IPPSessionPrefManager.js
modified
add_task
toolkit/components/ipprotection/tests/xpcshell/test_IPPSessionPrefManager.js
modified

Files Changed

  • toolkit/components/ipprotection/IPPSessionPrefManager.sys.mjs
  • toolkit/components/ipprotection/IPProtectionActivator.sys.mjs
  • toolkit/components/ipprotection/docs/Components.rst
  • toolkit/components/ipprotection/moz.build
  • toolkit/components/ipprotection/tests/xpcshell/test_IPPSessionPrefManager.js
  • toolkit/components/ipprotection/tests/xpcshell/xpcshell.toml
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…