Medium firefox Logic Error 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionAn attacker with control over a content process could potentially leverage the privileged UITour actor to leak sensitive information or escalate privileges.
ComponentDOM
Bug ClassLogic Error
Tracker1915280
Fix commit4ee141adaed3 (firefox) +124/-62
CISA KEVNot listed
CreditedAndrew McCreight
Disclosed2025-04-29

Changed Functions

FunctionChangeNotes
handleEvent
browser/components/uitour/UITourChild.sys.mjs
modified
isTestingOrigin
browser/components/uitour/UITourChild.sys.mjs
modified
if
browser/components/uitour/UITourChild.sys.mjs
modified
isSafeScheme
browser/components/uitour/UITourChild.sys.mjs
modified
ensureTrustedOrigin
browser/components/uitour/UITourChild.sys.mjs
modified
receiveMessage
browser/components/uitour/UITourChild.sys.mjs
modified
switch
browser/components/uitour/UITourChild.sys.mjs
modified
sendPageEvent
browser/components/uitour/UITourChild.sys.mjs
modified
receiveMessage
browser/components/uitour/UITourParent.sys.mjs
modified
switch
browser/components/uitour/UITourParent.sys.mjs
modified
if
browser/components/uitour/UITourParent.sys.mjs
modified
isTestingOrigin
browser/components/uitour/UITourUtils.sys.mjs
modified
if
browser/components/uitour/UITourUtils.sys.mjs
modified
ensureTrustedOrigin
browser/components/uitour/UITourUtils.sys.mjs
modified

Files Changed

  • browser/components/uitour/UITourChild.sys.mjs
  • browser/components/uitour/UITourParent.sys.mjs
  • browser/components/uitour/UITourUtils.sys.mjs
  • browser/components/uitour/moz.build
  • browser/components/uitour/test/browser_UITour_private_browsing.js
  • dom/chrome-webidl/WindowGlobalActors.webidl
  • dom/ipc/WindowGlobalChild.cpp
  • dom/ipc/WindowGlobalChild.h
diff --git a/browser/components/uitour/UITourChild.sys.mjs b/browser/components/uitour/UITourChild.sys.mjs
index 969b6923520..2df3b6e4c51 100644
--- a/browser/components/uitour/UITourChild.sys.mjs
+++ b/browser/components/uitour/UITourChild.sys.mjs
@@ -2,12 +2,11 @@
  * 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/. */
 
-const PREF_TEST_ORIGINS = "browser.uitour.testingOrigins";
-const UITOUR_PERMISSION = "uitour";
+import { UITourUtils } from "moz-src:///browser/components/uitour/UITourUtils.sys.mjs";
 
 export class UITourChild extends JSWindowActorChild {
   handleEvent(event) {
-    if (!this.ensureTrustedOrigin()) {
+    if (!UITourUtils.ensureTrustedOrigin(this.manager)) {
       return;
     }
 
@@ -18,62 +17,6 @@ export class UITourChild extends JSWindowActorChild {
     });
   }
 
-  isTestingOrigin(aURI) {
-    let testingOrigins = Services.prefs.getStringPref(PREF_TEST_ORIGINS, "");
-    if (!testingOrigins) {
-      return false;
-    }
-
-    // Allow any testing origins (comma-seperated).
-    for (let origin of testingOrigins.split(/\s*,\s*/)) {
-      try {
-        let testingURI = Services.io.newURI(origin);
-        if (aURI.prePath == testingURI.prePath) {
-          return true;
-        }
-      } catch (ex) {
-        console.error(ex);
-      }
-    }
-    return false;
-  }
-
-  // This function is copied from UITour.sys.mjs.
-  isSafeScheme(aURI) {
-    let allowedSchemes = new Set(["https", "about"]);
-    if (!allowedSchemes.has(aURI.scheme)) {
-      return false;
-    }
-
-    return true;
-  }
-
-  ensureTrustedOrigin() {
-    if (this.browsingContext.top != this.browsingContext) {
-      return false;
-    }
-
-    let uri = this.document.documentURIObject;
-
-    if (uri.schemeIs("chrome")) {
-      return true;
-    }
-
-    if (!this.isSafeScheme(uri)) {
-      return false;
-    }
-
-    let permission = Services.perms.testPermissionFromPrincipal(
-      this.document.nodePrincipal,
-      UITOUR_PERMISSION
-    );
-    if (permission == Services.perms.ALLOW_ACTION) {
-      return true;
-    }
-
-    return this.isTestingOrigin(uri);
-  }
-
   receiveMessage(aMessage) {
     switch (aMessage.name) {
       case "UITour:SendPageCallback":
@@ -86,7 +29,7 @@ export class UITourChild extends JSWindowActorChild {
   }
 
   sendPageEvent(type, detail) {
-    if (!this.ensureTrustedOrigin()) {
+    if (!UITourUtils.ensureTrustedOrigin(this.manager)) {
       return;
     }
 
diff --git a/browser/components/uitour/UITourParent.sys.mjs b/browser/components/uitour/UITourParent.sys.mjs
index 56222251074..61506d358db 100644
--- a/browser/components/uitour/UITourParent.sys.mjs
+++ b/browser/components/uitour/UITourParent.sys.mjs
@@ -3,9 +3,13 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 import { UITour } from "moz-src:///browser/components/uitour/UITour.sys.mjs";
+import { UITourUtils } from "moz-src:///browser/components/uitour/UITourUtils.sys.mjs";
 
 export class UITourParent extends JSWindowActorParent {
   receiveMessage(message) {
+    if (!UITourUtils.ensureTrustedOrigin(this.manager)) {
+      return;
+    }
     switch (message.name) {
       case "UITour:onPageEvent":
         if (this.manager.rootFrameLoader) {
diff --git a/browser/components/uitour/UITourUtils.sys.mjs b/browser/components/uitour/UITourUtils.sys.mjs
new file mode 100644
index 00000000000..b390b2b53dc
--- /dev/null
+++ b/browser/components/uitour/UITourUtils.sys.mjs
@@ -0,0 +1,84 @@
+/* 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/. */
+
+const PREF_TEST_ORIGINS = "browser.uitour.testingOrigins";
+const UITOUR_PERMISSION = "uitour";
+
+export let UITourUtils = {
+  /**
+   * Check if we've got a testing origin.
+   *
+   * @param {nsIURI} uri
+   *    The URI to check
+   * @returns {boolean}
+   *    Whether or not it's a testing origin.
+   */
+  isTestingOrigin(uri) {
+    let testingOrigins = Services.prefs.getStringPref(PREF_TEST_ORIGINS, "");
+    if (!testingOrigins) {
+      return false;
+    }
+
+    // Allow any testing origins (comma-seperated).
+    for (let origin of testingOrigins.split(/\s*,\s*/)) {
+      try {
+        let testingURI = Services.io.newURI(origin);
+        if (uri.prePath == testingURI.prePath) {
+          return true;
+        }
+      } catch (ex) {
+        console.error(ex);
+      }
+    }
+    return false;
+  },
+
+  /**
+   *
+   * @param {WindowGlobalChild|WindowGlobalParent} windowGlobal
+   *   The parent/child representation of a window global to check if we can
+   *   use UITour.
+   * @returns {boolean}
+   *   Whether or not we can use UITour here.
+   */
+  ensureTrustedOrigin(windowGlobal) {
+    // If we're not top-most or no longer current, bail out immediately.
+    if (windowGlobal.browsingContext.parent || !windowGlobal.isCurrentGlobal) {
+      return false;
+    }
+
+    let principal, uri;
+    // We can get either a WindowGlobalParent or WindowGlobalChild, depending on
+    // what process we're called in, and determining the secure context-ness and
+    // principal + URI needs different approaches based on this.
+    if (WindowGlobalParent.isInstance(windowGlobal)) {
+      if (!windowGlobal.browsingContext.secureBrowserUI?.isSecureContext) {
+        return false;
+      }
+      principal = windowGlobal.documentPrincipal;
+      uri = windowGlobal.documentURI;
+    } else {
+      if (!windowGlobal.contentWindow?.isSecureContext) {
+        return false;
+      }
+      let document = windowGlobal.contentWindow.document;
+      principal = document?.nodePrincipal;
+      uri = document?.documentURIObject;
+    }
+
+    if (!principal) {
+      return false;
+    }
+
+    let permission = Services.perms.testPermissionFromPrincipal(
+      principal,
+      UITOUR_PERMISSION
+    );
+    if (permission == Services.perms.ALLOW_ACTION) {
+      return true;
+    }
+
+    return uri && this.isTestingOrigin(uri);
+  },
+};
diff --git a/browser/components/uitour/moz.build b/browser/components/uitour/moz.build
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/browser/components/uitour/test/browser_UITour_private_browsing.js b/browser/components/uitour/test/browser_UITour_private_browsing.js
index 0c5a52f6674..d69bb30fbb9 100644
--- a/browser/components/uitour/test/browser_UITour_private_browsing.js
+++ b/browser/components/uitour/test/browser_UITour_private_browsing.js
@@ -16,6 +16,10 @@ add_task(async function test_privatebrowsing_window() {
   const ABOUT_ORIGIN_WITH_UITOUR_DEFAULT = "about:newtab";
   const HTTPS_ORIGIN_WITH_UITOUR_DEFAULT = "https://www.mozilla.org";
 
+  let { UITourUtils } = ChromeUtils.importESModule(
+    "moz-src:///browser/components/uitour/UITourUtils.sys.mjs"
+  );
+
   let win = await BrowserTestUtils.openNewBrowserWindow({ private: true });
   let browser = win.gBrowser.selectedBrowser;
 
@@ -26,10 +30,21 @@ add_task(async function test_privatebrowsing_window() {
     BrowserTestUtils.startLoadingURIString(browser, uri);
     await BrowserTestUtils.browserLoaded(browser);
 
+    Assert.ok(
+      UITourUtils.ensureTrustedOrigin(
+        browser.browsingContext.currentWindowGlobal
+      ),
+      "Page should be considered trusted for UITour in the parent."
+    );
+
     await SpecialPowers.spawn(browser, [], async () => {
       let actor = content.windowGlobalChild.getActor("UITour");
+      // eslint-disable-next-line no-shadow
+      let { UITourUtils } = ChromeUtils.importESModule(
+        "moz-src:///browser/components/uitour/UITourUtils.sys.mjs"
+      );
       Assert.ok(
-        actor.ensureTrustedOrigin(),
+        UITourUtils.ensureTrustedOrigin(actor.manager),
         "Page should be considered trusted for UITour."
       );
     });
Loading diff…