High firefox Logic Error 🔧 Commit mapped

Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionPrivilege escalation in the Messaging System component
ComponentCore
Bug ClassLogic Error
Tracker2015305
Fix commit35c67d91b9a1 (firefox) +213/-199
CISA KEVNot listed
CreditedRichard Belisle
Disclosed2026-02-24

Changed Functions

FunctionChangeNotes
handleEvent
browser/components/messagepreview/actors/AboutMessagePreviewChild.sys.mjs
modified
actorCreated
browser/components/messagepreview/actors/AboutMessagePreviewChild.sys.mjs
modified
exportFunctions
browser/components/messagepreview/actors/AboutMessagePreviewChild.sys.mjs
modified
if
browser/components/messagepreview/actors/AboutMessagePreviewChild.sys.mjs
modified
for
browser/components/messagepreview/actors/AboutMessagePreviewChild.sys.mjs
modified
MPToggleLights
browser/components/messagepreview/actors/AboutMessagePreviewChild.sys.mjs
modified
MPShowMessage
browser/components/messagepreview/actors/AboutMessagePreviewChild.sys.mjs
modified
MPShouldShowHint
browser/components/messagepreview/actors/AboutMessagePreviewChild.sys.mjs
modified
if
browser/components/messagepreview/actors/AboutMessagePreviewParent.sys.mjs
modified
receiveMessage
browser/components/messagepreview/actors/AboutMessagePreviewParent.sys.mjs
modified
switch
browser/components/messagepreview/actors/AboutMessagePreviewParent.sys.mjs
modified

Files Changed

  • browser/components/DesktopActorRegistry.sys.mjs
  • browser/components/about/AboutRedirector.cpp
  • browser/components/messagepreview/actors/AboutMessagePreviewChild.sys.mjs
  • browser/components/messagepreview/actors/AboutMessagePreviewParent.sys.mjs
  • browser/components/messagepreview/messagepreview.css
  • browser/components/messagepreview/messagepreview.js
  • browser/components/messagepreview/tests/browser/browser.toml
  • browser/components/messagepreview/tests/browser/browser_messagepreview_receivemessage.js
  • browser/components/messagepreview/tests/browser/browser_messagepreview_show_bookmarks_bar.js
  • browser/components/messagepreview/tests/browser/browser_messagepreview_show_cfr.js
  • browser/components/messagepreview/tests/browser/browser_messagepreview_show_featurecallout.js
  • browser/components/messagepreview/tests/browser/browser_messagepreview_show_infobar.js
  • browser/components/messagepreview/tests/browser/browser_messagepreview_show_invalidmessage.js
  • browser/components/messagepreview/tests/browser/browser_messagepreview_show_privatebrowsing.js
  • browser/components/messagepreview/tests/browser/browser_messagepreview_show_spotlight.js
  • browser/components/messagepreview/tests/browser/head.js
diff --git a/browser/components/DesktopActorRegistry.sys.mjs b/browser/components/DesktopActorRegistry.sys.mjs
index 6bb71df3fee..4f309368c07 100644
--- a/browser/components/DesktopActorRegistry.sys.mjs
+++ b/browser/components/DesktopActorRegistry.sys.mjs
@@ -135,6 +135,9 @@ let JSWINDOWACTORS = {
       },
     },
     matches: ["about:messagepreview", "about:messagepreview?*"],
+    remoteTypes: ["privilegedabout"],
+    enablePreference:
+      "browser.newtabpage.activity-stream.asrouter.devtoolsEnabled",
   },
 
   AboutPrivateBrowsing: {
diff --git a/browser/components/about/AboutRedirector.cpp b/browser/components/about/AboutRedirector.cpp
index 0754291ab98..c63491d2fa9 100644
--- a/browser/components/about/AboutRedirector.cpp
+++ b/browser/components/about/AboutRedirector.cpp
@@ -116,6 +116,7 @@ static const RedirEntry kRedirMap[] = {
     {"messagepreview",
      "chrome://browser/content/messagepreview/messagepreview.html",
      nsIAboutModule::URI_MUST_LOAD_IN_CHILD |
+         nsIAboutModule::URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS |
          nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
          nsIAboutModule::ALLOW_SCRIPT | nsIAboutModule::HIDE_FROM_ABOUTABOUT},
     {"settings", "chrome://browser/content/preferences/preferences.xhtml",
diff --git a/browser/components/messagepreview/actors/AboutMessagePreviewChild.sys.mjs b/browser/components/messagepreview/actors/AboutMessagePreviewChild.sys.mjs
index cd0944d3c7c..3e8234ebdb7 100644
--- a/browser/components/messagepreview/actors/AboutMessagePreviewChild.sys.mjs
+++ b/browser/components/messagepreview/actors/AboutMessagePreviewChild.sys.mjs
@@ -2,9 +2,20 @@
  * 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/. */
 
+import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
+
+const lazy = XPCOMUtils.declareLazy({
+  log: () => {
+    const { Logger } = ChromeUtils.importESModule(
+      "resource://messaging-system/lib/Logger.sys.mjs"
+    );
+    return new Logger("AboutMessagePreviewChild");
+  },
+});
+
 export class AboutMessagePreviewChild extends JSWindowActorChild {
   handleEvent(event) {
-    console.log(`Received page event ${event.type}`);
+    lazy.log.debug(`Received page event ${event.type}`);
   }
 
   actorCreated() {
@@ -13,12 +24,7 @@ export class AboutMessagePreviewChild extends JSWindowActorChild {
 
   exportFunctions() {
     if (this.contentWindow) {
-      for (const name of [
-        "MPShowMessage",
-        "MPIsEnabled",
-        "MPShouldShowHint",
-        "MPToggleLights",
-      ]) {
+      for (const name of ["MPShowMessage", "MPIsEnabled", "MPToggleLights"]) {
         Cu.exportFunction(this[name].bind(this), this.contentWindow, {
           defineAs: name,
         });
@@ -42,11 +48,11 @@ export class AboutMessagePreviewChild extends JSWindowActorChild {
   /**
    * Check the browser theme and switch it.
    */
-  MPToggleLights() {
+  async MPToggleLights() {
     const isDark = this.contentWindow.matchMedia(
       "(prefers-color-scheme: dark)"
     ).matches;
-    this.sendAsyncMessage(`MessagePreview:CHANGE_THEME`, { isDark });
+    await this.sendQuery(`MessagePreview:CHANGE_THEME`, { isDark });
   }
 
   /**
@@ -55,16 +61,7 @@ export class AboutMessagePreviewChild extends JSWindowActorChild {
    *
    * @param {object} message
    */
-  MPShowMessage(message) {
-    this.sendAsyncMessage(`MessagePreview:SHOW_MESSAGE`, message);
-  }
-
-  /**
-   * Check if a hint should be shown about how to enable Message Preview.
-   *
-   * @returns {boolean}
-   */
-  MPShouldShowHint() {
-    return !this.MPIsEnabled();
+  async MPShowMessage(message) {
+    await this.sendQuery(`MessagePreview:SHOW_MESSAGE`, message);
   }
 }
diff --git a/browser/components/messagepreview/actors/AboutMessagePreviewParent.sys.mjs b/browser/components/messagepreview/actors/AboutMessagePreviewParent.sys.mjs
index 32501487c57..0dc0040a8ec 100644
--- a/browser/components/messagepreview/actors/AboutMessagePreviewParent.sys.mjs
+++ b/browser/components/messagepreview/actors/AboutMessagePreviewParent.sys.mjs
@@ -3,12 +3,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/. */
 
+import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
 import { ASRouter } from "resource:///modules/asrouter/ASRouter.sys.mjs";
 import { JsonSchema } from "resource://gre/modules/JsonSchema.sys.mjs";
 
-const lazy = {};
-
-ChromeUtils.defineESModuleGetters(lazy, {
+const lazy = XPCOMUtils.declareLazy({
   AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
   BookmarksBarButton: "resource:///modules/asrouter/BookmarksBarButton.sys.mjs",
   CFRPageActions: "resource:///modules/asrouter/CFRPageActions.sys.mjs",
@@ -20,6 +19,13 @@ ChromeUtils.defineESModuleGetters(lazy, {
   SpecialMessageActions:
     "resource://messaging-system/lib/SpecialMessageActions.sys.mjs",
   Spotlight: "resource:///modules/asrouter/Spotlight.sys.mjs",
+
+  log: () => {
+    const { Logger } = ChromeUtils.importESModule(
+      "resource://messaging-system/lib/Logger.sys.mjs"
+    );
+    return new Logger("AboutMessagePreviewParent");
+  },
 });
 
 const SWITCH_THEMES = {
@@ -114,7 +120,7 @@ export class AboutMessagePreviewParent extends JSWindowActorParent {
         }
 
         screen.anchors = [...existingAnchors, fallbackAnchor];
-        console.log("ANCHORS: ", screen.anchors);
+        lazy.log.debug("ANCHORS: ", screen.anchors);
       }
       // Try showing again
       await lazy.FeatureCalloutBroker.showFeatureCallout(browser, message);
@@ -137,7 +143,7 @@ export class AboutMessagePreviewParent extends JSWindowActorParent {
     try {
       message = JSON.parse(data);
     } catch (e) {
-      console.error("Could not parse message", e);
+      lazy.log.error("Could not parse message", e);
       return;
     }
 
@@ -148,7 +154,7 @@ export class AboutMessagePreviewParent extends JSWindowActorParent {
       ).then(rsp => rsp.json());
       const result = JsonSchema.validate(message, schema);
       if (!result.valid) {
-        console.error(
+        lazy.log.error(
           `Invalid message: ${JSON.stringify(result.errors, undefined, 2)}`
         );
       }
@@ -176,25 +182,27 @@ export class AboutMessagePreviewParent extends JSWindowActorParent {
         this.showPrivateBrowsingMessage(message, browser);
         return;
       default:
-        console.error(`Unsupported message template ${message.template}`);
+        lazy.log.error(`Unsupported message template ${message.template}`);
     }
   }
 
-  receiveMessage(message) {
+  async receiveMessage(message) {
     // validationEnabled is used for testing
     const { name, data, validationEnabled } = message;
 
     switch (name) {
       case "MessagePreview:SHOW_MESSAGE":
-        this.showMessage(data, validationEnabled);
+        await this.showMessage(data, validationEnabled);
         return;
       case "MessagePreview:CHANGE_THEME": {
         const theme = data.isDark ? SWITCH_THEMES.LIGHT : SWITCH_THEMES.DARK;
-        lazy.AddonManager.getAddonByID(theme).then(addon => addon.enable());
+        await lazy.AddonManager.getAddonByID(theme).then(addon =>
+          addon.enable()
+        );
         return;
       }
       default:
-        console.log(`Unexpected event ${name} was not handled.`);
+        lazy.log.debug(`Unexpected event ${name} was not handled.`);
     }
   }
 }
diff --git a/browser/components/messagepreview/messagepreview.css b/browser/components/messagepreview/messagepreview.css
index 18ade19f50d..7d25913e2c2 100644
--- a/browser/components/messagepreview/messagepreview.css
+++ b/browser/components/messagepreview/messagepreview.css
@@ -34,15 +34,3 @@ body {
     transform: scaleY(-1);
   }
 }
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/browser/components/messagepreview/tests/browser/browser.toml b/browser/components/messagepreview/tests/browser/browser.toml
index 827436e4748..880abe1e970 100644
--- a/browser/components/messagepreview/tests/browser/browser.toml
+++ b/browser/components/messagepreview/tests/browser/browser.toml
@@ -20,8 +20,5 @@ prefs = [
 ["browser_messagepreview_show_invalidmessage.js"]
 
 ["browser_messagepreview_show_privatebrowsing.js"]
-skip-if = [
-  "true", # Bug 1946629
-]
 
 ["browser_messagepreview_show_spotlight.js"]
diff --git a/browser/components/messagepreview/tests/browser/browser_messagepreview_receivemessage.js b/browser/components/messagepreview/tests/browser/browser_messagepreview_receivemessage.js
index 58ec7a65d4a..8382bdd6435 100644
--- a/browser/components/messagepreview/tests/browser/browser_messagepreview_receivemessage.js
+++ b/browser/components/messagepreview/tests/browser/browser_messagepreview_receivemessage.js
@@ -4,24 +4,40 @@ const { AboutMessagePreviewParent } = ChromeUtils.importESModule(
   "resource:///actors/AboutWelcomeParent.sys.mjs"
 );
 
+let messageSandbox;
+add_setup(async function () {
+  messageSandbox = sinon.createSandbox();
+  registerCleanupFunction(() => {
+    messageSandbox.restore();
+  });
+});
+
 /**
  * Test the parent receiveMessage function
  */
 add_task(async function test_receive_message() {
-  const messageSandbox = sinon.createSandbox();
   let { cleanup, browser } = await openMessagePreviewTab();
   let aboutMessagePreviewActor = await getAboutMessagePreviewParent(browser);
   messageSandbox.spy(aboutMessagePreviewActor, "receiveMessage");
-  registerCleanupFunction(() => {
-    messageSandbox.restore();
-  });
 
-  await aboutMessagePreviewActor.receiveMessage(
-    "MessagePreview:SHOW_MESSAGE",
-    {}
-  );
+  await aboutMessagePreviewActor.receiveMessage({
+    name: "MessagePreview:SHOW_MESSAGE",
+    target: {
+      browsingContext: {
+        currentRemoteType: "privilegedabout",
+      },
+    },
+  });
 
-  await aboutMessagePreviewActor.receiveMessage("MessagePreview:CHANGE_THEME");
+  await aboutMessagePreviewActor.receiveMessage({
+    name: "MessagePreview:CHANGE_THEME",
+    data: {},
+    target: {
+      browsingContext: {
+        currentRemoteType: "privilegedabout",
+      },
+    },
+  });
 
   const { callCount } = aboutMessagePreviewActor.receiveMessage;
   let messageCall;
@@ -29,9 +45,9 @@ add_task(async function test_receive_message() {
   for (let i = 0; i < callCount; i++) {
     const call = aboutMessagePreviewActor.receiveMessage.getCall(i);
     info(`Call #${i}: ${JSON.stringify(call.args[0])}`);
-    if (call.calledWithMatch("MessagePreview:SHOW_MESSAGE")) {
+    if (call.calledWithMatch({ name: "MessagePreview:SHOW_MESSAGE" })) {
       messageCall = call;
-    } else if (call.calledWithMatch("MessagePreview:CHANGE_THEME")) {
+    } else if (call.calledWithMatch({ name: "MessagePreview:CHANGE_THEME" })) {
       themeCall = call;
     }
   }
@@ -39,15 +55,16 @@ add_task(async function test_receive_message() {
   Assert.greaterOrEqual(callCount, 2, `${callCount} receive spy was called`);
 
   Assert.equal(
-    messageCall.args[0],
+    messageCall.args[0]?.name,
     "MessagePreview:SHOW_MESSAGE",
     "Got call to handle showing a message"
   );
   Assert.equal(
-    themeCall.args[0],
+    themeCall.args[0]?.name,
     "MessagePreview:CHANGE_THEME",
     "Got call to handle changing the theme"
   );
 
+  messageSandbox.restore();
   await cleanup();
 });
diff --git a/browser/components/messagepreview/tests/browser/browser_messagepreview_show_bookmarks_bar.js b/browser/components/messagepreview/tests/browser/browser_messagepreview_show_bookmarks_bar.js
index 09fe1a00e43..722303f7cc9 100644
--- a/browser/components/messagepreview/tests/browser/browser_messagepreview_show_bookmarks_bar.js
+++ b/browser/components/messagepreview/tests/browser/browser_messagepreview_show_bookmarks_bar.js
@@ -4,6 +4,8 @@ const { AboutMessagePreviewParent } = ChromeUtils.importESModule(
   "resource:///actors/AboutWelcomeParent.sys.mjs"
 );
 
+let messageSandbox;
+
 const TEST_BOOKMARKS_BAR_MESSAGE = {
   id: "TEST_BMB_BAR_BUTTON",
   groups: [],
@@ -26,30 +28,32 @@ const TEST_BOOKMARKS_BAR_MESSAGE = {
   targeting: "true",
 };
 
+add_setup(async function () {
+  messageSandbox = sinon.createSandbox();
+  registerCleanupFunction(() => {
+    messageSandbox.restore();
+  });
+});
+
 add_task(async function test_show_bookmarks_bar_button_message() {
-  const messageSandbox = sinon.createSandbox();
   let { cleanup, browser } = await openMessagePreviewTab();
   let aboutMessagePreviewActor = await getAboutMessagePreviewParent(browser);
   messageSandbox.spy(aboutMessagePreviewActor, "showMessage");
-  registerCleanupFunction(() => {
-    messageSandbox.restore();
-  });
 
-  await aboutMessagePreviewActor.receiveMessage({
-    name: "MessagePreview:SHOW_MESSAGE",
-    data: JSON.stringify(TEST_BOOKMARKS_BAR_MESSAGE),
-    validationEnabled: false,
-  });
+  await SpecialPowers.spawn(browser, [TEST_BOOKMARKS_BAR_MESSAGE], message =>
+    content.wrappedJSObject.MPShowMessage(JSON.stringify(message))
+  );
 
   const { callCount } = aboutMessagePreviewActor.showMessage;
   Assert.greaterOrEqual(callCount, 1, "showMessage was called");
 
-  await ContentTaskUtils.waitForCondition(
+  await BrowserTestUtils.waitForCondition(
     () => selectorIsVisible("#fxms-bmb-button"),
     "Bookmarks toolbar button should be visible"
   );
 
   // Remove the button, so repeated tests don't fail
   await CustomizableUI.destroyWidget("fxms-bmb-button");
+  messageSandbox.restore();
   await cleanup();
 });
diff --git a/browser/components/messagepreview/tests/browser/browser_messagepreview_show_cfr.js b/browser/components/messagepreview/tests/browser/browser_messagepreview_show_cfr.js
index 592cbeede0d..708a81d10e8 100644
--- a/browser/components/messagepreview/tests/browser/browser_messagepreview_show_cfr.js
+++ b/browser/components/messagepreview/tests/browser/browser_messagepreview_show_cfr.js
@@ -4,6 +4,8 @@ const { AboutMessagePreviewParent } = ChromeUtils.importESModule(
   "resource:///actors/AboutWelcomeParent.sys.mjs"
 );
 
+let messageSandbox;
+
 const TEST_CFR_MESSAGE = {
   content: {
     text: {
@@ -57,19 +59,21 @@ const TEST_CFR_MESSAGE = {
   id: "CFR_FULL_VIDEO_SUPPORT_EN",
 };
 
+add_setup(async function () {
+  messageSandbox = sinon.createSandbox();
+  registerCleanupFunction(() => {
+    messageSandbox.restore();
+  });
+});
+
 add_task(async function test_show_cfr_message() {
-  const messageSandbox = sinon.createSandbox();
   let { cleanup, browser } = await openMessagePreviewTab();
   let aboutMessagePreviewActor = await getAboutMessagePreviewParent(browser);
   messageSandbox.spy(aboutMessagePreviewActor, "showMessage");
-  registerCleanupFunction(() => {
-    messageSandbox.restore();
-  });
 
-  await aboutMessagePreviewActor.receiveMessage({
-    name: "MessagePreview:SHOW_MESSAGE",
-    data: JSON.stringify(TEST_CFR_MESSAGE),
-  });
+  await SpecialPowers.spawn(browser, [TEST_CFR_MESSAGE], message =>
+    content.wrappedJSObject.MPShowMessage(JSON.stringify(message))
+  );
 
   const { callCount } = aboutMessagePreviewActor.showMessage;
   Assert.greaterOrEqual(callCount, 1, "showMessage was called");
@@ -85,5 +89,6 @@ add_task(async function test_show_cfr_message() {
   );
 
   await clearNotifications();
+  messageSandbox.restore();
   await cleanup();
 });
diff --git a/browser/components/messagepreview/tests/browser/browser_messagepreview_show_featurecallout.js b/browser/components/messagepreview/tests/browser/browser_messagepreview_show_featurecallout.js
index ec4a9afe700..69ce2e3a128 100644
--- a/browser/components/messagepreview/tests/browser/browser_messagepreview_show_featurecallout.js
+++ b/browser/components/messagepreview/tests/browser/browser_messagepreview_show_featurecallout.js
@@ -4,6 +4,8 @@ const { AboutMessagePreviewParent } = ChromeUtils.importESModule(
   "resource:///actors/AboutWelcomeParent.sys.mjs"
 );
 
+let messageSandbox;
+
 // A feature callout that needs no modification
 const TEST_HAPPY_FEATURE_CALLOUT_MESSAGE = {
   id: "TEST_HAPPY_FEATURE_CALLOUT",
@@ -276,32 +278,28 @@ const TEST_VERY_BAD_FEATURE_CALLOUT = {
   provider: "panel_local_testing",
 };
 
+add_setup(async function () {
+  messageSandbox = sinon.createSandbox();
+  registerCleanupFunction(() => {
+    messageSandbox.restore();
+  });
+});
+
 /**
  * Test each version of the feature callout
  */
 add_task(async function test_show_happy_feature_callout_message() {
-  const messageSandbox = sinon.createSandbox();
   // FeatureCallout needs a new window
   const win = await BrowserTestUtils.openNewBrowserWindow();
-  let tab = await BrowserTestUtils.openNewForegroundTab(
-    win.gBrowser,
-    "about:messagepreview",
-    true
-  );
-
-  let aboutMessagePreviewActor = await getAboutMessagePreviewParent(
-    tab.linkedBrowser
-  );
+  let { browser } = await openMessagePreviewTab(win.gBrowser);
+  let aboutMessagePreviewActor = await getAboutMessagePreviewParent(browser);
   messageSandbox.spy(aboutMessagePreviewActor, "showMessage");
-  registerCleanupFunction(() => {
-    messageSandbox.restore();
-  });
 
-  await aboutMessagePreviewActor.receiveMessage({
-    name: "MessagePreview:SHOW_MESSAGE",
-    data: JSON.stringify(TEST_HAPPY_FEATURE_CALLOUT_MESSAGE),
-    validationEnabled: false,
-  });
+  await SpecialPowers.spawn(
+    browser,
+    [TEST_HAPPY_FEATURE_CALLOUT_MESSAGE],
+    message => content.wrappedJSObject.MPShowMessage(JSON.stringify(message))
+  );
 
   const { callCount } = aboutMessagePreviewActor.showMessage;
   Assert.greaterOrEqual(callCount, 1, "showMessage was called");
@@ -320,32 +318,23 @@ add_task(async function test_show_happy_feature_callout_message() {
 
   await waitForClick("button.dismiss-button", win);
   await dialogClosed(win);
+  messageSandbox.restore();
   await BrowserTestUtils.closeWindow(win);
 });
 
 add_task(async function test_show_feature_callout_without_trigger() {
-  const messageSandbox = sinon.createSandbox();
   // FeatureCallout needs a new window
   const win = await BrowserTestUtils.openNewBrowserWindow();
-  let tab = await BrowserTestUtils.openNewForegroundTab(
-    win.gBrowser,
-    "about:messagepreview",
-    true
-  );
-
-  let aboutMessagePreviewActor = await getAboutMessagePreviewParent(
-    tab.linkedBrowser
-  );
+  let { browser } = await openMessagePreviewTab(win.gBrowser);
+  let aboutMessagePreviewActor = await getAboutMessagePreviewParent(browser);
   messageSandbox.spy(aboutMessagePreviewActor, "showMessage");
-  registerCleanupFunction(() => {
-    messageSandbox.restore();
-  });
 
-  await aboutMessagePreviewActor.receiveMessage({
-    name: "MessagePreview:SHOW_MESSAGE",
-    data: JSON.stringify(TEST_FEATURE_CALLOUT_NO_TRIGGER),
-    validationEnabled: false,
-  });
+  await SpecialPowers.spawn(
+    browser,
+    [TEST_FEATURE_CALLOUT_NO_TRIGGER],
... (truncated)
Loading diff…