Medium firefox Logic Error 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionAn attacker who enumerated resources from the WebCompat extension could have obtained a persistent UUID that identified the browser, and persisted between containers and normal/private browsing mode, but not profiles.
ComponentToolkit
Bug ClassLogic Error
Tracker1717672
Fix commitacefe30a8b6f (firefox) +69/-1
CISA KEVNot listed
CreditedRob Wu
Disclosed2025-06-24

Changed Functions

FunctionChangeNotes
add_task
browser/extensions/webcompat/tests/browser/browser_uuid_migration.js
modified
get
toolkit/components/extensions/Extension.sys.mjs
modified
if
toolkit/components/extensions/Extension.sys.mjs
modified

Files Changed

  • browser/extensions/webcompat/moz.build
  • browser/extensions/webcompat/tests/browser/browser_aboutcompat.js
  • browser/extensions/webcompat/tests/browser/browser_uuid_migration.js
  • browser/extensions/webcompat/tests/browser/browser_uuid_migration.toml
  • toolkit/components/extensions/Extension.sys.mjs
diff --git a/browser/extensions/webcompat/moz.build b/browser/extensions/webcompat/moz.build
index db7f2c6bb5b..19689b83a42 100644
--- a/browser/extensions/webcompat/moz.build
+++ b/browser/extensions/webcompat/moz.build
@@ -12,7 +12,10 @@ XPCOM_MANIFESTS += [
     "components.conf",
 ]
 
-BROWSER_CHROME_MANIFESTS += ["tests/browser/browser.toml"]
+BROWSER_CHROME_MANIFESTS += [
+    "tests/browser/browser.toml",
+    "tests/browser/browser_uuid_migration.toml",
+]
 
 with Files("**"):
     BUG_COMPONENT = ("Web Compatibility", "Tooling & Investigations")
diff --git a/browser/extensions/webcompat/tests/browser/browser_aboutcompat.js b/browser/extensions/webcompat/tests/browser/browser_aboutcompat.js
index ed89739c77d..110161bc165 100644
--- a/browser/extensions/webcompat/tests/browser/browser_aboutcompat.js
+++ b/browser/extensions/webcompat/tests/browser/browser_aboutcompat.js
@@ -8,6 +8,12 @@ add_task(async function test_about_compat_loads_properly() {
   });
 
   await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+    is(
+      content.origin,
+      "moz-extension://9a310967-e580-48bf-b3e8-4eafebbc122d",
+      "Expected origin of about:compat"
+    );
+
     await ContentTaskUtils.waitForCondition(
       () => content.document.querySelector("#interventions tr[data-id]"),
       "interventions are listed"
diff --git a/browser/extensions/webcompat/tests/browser/browser_uuid_migration.js b/browser/extensions/webcompat/tests/browser/browser_uuid_migration.js
new file mode 100644
index 00000000000..f3dc5df5949
--- /dev/null
+++ b/browser/extensions/webcompat/tests/browser/browser_uuid_migration.js
@@ -0,0 +1,33 @@
+"use strict";
+
+// Sanity check: The setup in the toml file is effective at fixing the uuid
+// for at least one extension.
+add_task(async function test_sanity_check_uuid_fixed_by_pref() {
+  is(
+    WebExtensionPolicy.getByID("pictureinpicture@mozilla.org")
+      .mozExtensionHostname,
+    "f00df00d-2222-f00d-8888-012345678900",
+    "Non-webcompat uuid is fixed by pref in browser_uuid_migration.toml"
+  );
+});
+
+add_task(async function test_webcompat_migrates_existing_uuid_pref() {
+  const expectedWebCompatUUID = "9a310967-e580-48bf-b3e8-4eafebbc122d";
+  Assert.notEqual(
+    expectedWebCompatUUID,
+    "f00df00d-1111-f00d-8888-012345678900",
+    "Sanity check: Expected UUID differs from browser_uuid_migration.toml"
+  );
+
+  is(
+    WebExtensionPolicy.getByID("webcompat@mozilla.org").mozExtensionHostname,
+    expectedWebCompatUUID,
+    "webcompat add-on has fixed UUID"
+  );
+
+  let uuids = Services.prefs.getStringPref("extensions.webextensions.uuids");
+  ok(
+    uuids.includes(`"webcompat@mozilla.org":"${expectedWebCompatUUID}"`),
+    `Pref value (${uuids}) should contain: ${expectedWebCompatUUID}`
+  );
+});
diff --git a/browser/extensions/webcompat/tests/browser/browser_uuid_migration.toml b/browser/extensions/webcompat/tests/browser/browser_uuid_migration.toml
new file mode 100644
index 00000000000..3cd9869d6d5
--- /dev/null
+++ b/browser/extensions/webcompat/tests/browser/browser_uuid_migration.toml
@@ -0,0 +1,7 @@
+[DEFAULT]
+tags = "webextensions"
+# Must be set in [DEFAULT] to ensure that the pref is set before the browser starts.
+# We choose two built-in extensions here: webcompat and another built-in extension.
+prefs = ['extensions.webextensions.uuids={"webcompat@mozilla.org":"f00df00d-1111-f00d-8888-012345678900","pictureinpicture@mozilla.org":"f00df00d-2222-f00d-8888-012345678900"}']
+
+["browser_uuid_migration.js"]
diff --git a/toolkit/components/extensions/Extension.sys.mjs b/toolkit/components/extensions/Extension.sys.mjs
index ff513e8674a..854ee8bcddf 100644
--- a/toolkit/components/extensions/Extension.sys.mjs
+++ b/toolkit/components/extensions/Extension.sys.mjs
@@ -391,6 +391,8 @@ const LOGGER_ID_BASE = "addons.webextension.";
 const UUID_MAP_PREF = "extensions.webextensions.uuids";
 const LEAVE_STORAGE_PREF = "extensions.webextensions.keepStorageOnUninstall";
 const LEAVE_UUID_PREF = "extensions.webextensions.keepUuidOnUninstall";
+const WEBCOMPAT_ADDON_ID = "webcompat@mozilla.org";
+const WEBCOMPAT_UUID = "9a310967-e580-48bf-b3e8-4eafebbc122d";
 
 // All moz-extension URIs use a machine-specific UUID rather than the
 // extension's own ID in the host component. This makes it more
@@ -416,6 +418,23 @@ var UUIDMap = {
   get(id, create = true) {
     let map = this._read();
 
+    // In general, the UUID should not change once assigned because it may be
+    // stored elsewhere within the profile directory, when the extension URL is
+    // exposed (e.g. history, bookmarks, site permissions, web or extension
+    // APIs that associate data with the extension principal or origin).
+    // The webcompat add-on does not rely on the persisted uuid, so we can
+    // simply migrate the uuid below, see bug 1717672.
+    if (id === WEBCOMPAT_ADDON_ID) {
+      if (!create && !(id in map)) {
+        return null;
+      }
+      if (map[id] !== WEBCOMPAT_UUID) {
+        map[id] = WEBCOMPAT_UUID;
+        this._write(map);
+      }
+      return WEBCOMPAT_UUID;
+    }
+
     if (id in map) {
       return map[id];
     }
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/browser/extensions/webcompat/tests/browser/browser_aboutcompat.js b/browser/extensions/webcompat/tests/browser/browser_aboutcompat.js
index ed89739c77d..110161bc165 100644
--- a/browser/extensions/webcompat/tests/browser/browser_aboutcompat.js
+++ b/browser/extensions/webcompat/tests/browser/browser_aboutcompat.js
@@ -8,6 +8,12 @@ add_task(async function test_about_compat_loads_properly() {
   });
 
   await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+    is(
+      content.origin,
+      "moz-extension://9a310967-e580-48bf-b3e8-4eafebbc122d",
+      "Expected origin of about:compat"
+    );
+
     await ContentTaskUtils.waitForCondition(
       () => content.document.querySelector("#interventions tr[data-id]"),
       "interventions are listed"
diff --git a/browser/extensions/webcompat/tests/browser/browser_uuid_migration.js b/browser/extensions/webcompat/tests/browser/browser_uuid_migration.js
new file mode 100644
index 00000000000..f3dc5df5949
--- /dev/null
+++ b/browser/extensions/webcompat/tests/browser/browser_uuid_migration.js
@@ -0,0 +1,33 @@
+"use strict";
+
+// Sanity check: The setup in the toml file is effective at fixing the uuid
+// for at least one extension.
+add_task(async function test_sanity_check_uuid_fixed_by_pref() {
+  is(
+    WebExtensionPolicy.getByID("pictureinpicture@mozilla.org")
+      .mozExtensionHostname,
+    "f00df00d-2222-f00d-8888-012345678900",
+    "Non-webcompat uuid is fixed by pref in browser_uuid_migration.toml"
+  );
+});
+
+add_task(async function test_webcompat_migrates_existing_uuid_pref() {
+  const expectedWebCompatUUID = "9a310967-e580-48bf-b3e8-4eafebbc122d";
+  Assert.notEqual(
+    expectedWebCompatUUID,
+    "f00df00d-1111-f00d-8888-012345678900",
+    "Sanity check: Expected UUID differs from browser_uuid_migration.toml"
+  );
+
+  is(
+    WebExtensionPolicy.getByID("webcompat@mozilla.org").mozExtensionHostname,
+    expectedWebCompatUUID,
+    "webcompat add-on has fixed UUID"
+  );
+
+  let uuids = Services.prefs.getStringPref("extensions.webextensions.uuids");
+  ok(
+    uuids.includes(`"webcompat@mozilla.org":"${expectedWebCompatUUID}"`),
+    `Pref value (${uuids}) should contain: ${expectedWebCompatUUID}`
+  );
+});
diff --git a/browser/extensions/webcompat/tests/browser/browser_uuid_migration.toml b/browser/extensions/webcompat/tests/browser/browser_uuid_migration.toml
new file mode 100644
index 00000000000..3cd9869d6d5
--- /dev/null
+++ b/browser/extensions/webcompat/tests/browser/browser_uuid_migration.toml
@@ -0,0 +1,7 @@
+[DEFAULT]
+tags = "webextensions"
+# Must be set in [DEFAULT] to ensure that the pref is set before the browser starts.
+# We choose two built-in extensions here: webcompat and another built-in extension.
+prefs = ['extensions.webextensions.uuids={"webcompat@mozilla.org":"f00df00d-1111-f00d-8888-012345678900","pictureinpicture@mozilla.org":"f00df00d-2222-f00d-8888-012345678900"}']
+
+["browser_uuid_migration.js"]
Loading diff…