High firefox Logic Error 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionPrivilege escalation in the WebExtensions component
ComponentToolkit
Bug ClassLogic Error
Tracker2060235
Fix commit0a819607c3b8 (firefox) +42/-57
CISA KEVNot listed
CreditedQuy Pham
Disclosed2026-09-15

Changed Functions

FunctionChangeNotes
if
toolkit/components/extensions/ExtensionParent.sys.mjs
modified
constructor
toolkit/components/extensions/ExtensionParent.sys.mjs
modified
openNative
toolkit/components/extensions/ExtensionParent.sys.mjs
modified
trackNativeAppPort
toolkit/components/extensions/ExtensionParent.sys.mjs
modified
untrackNativeAppPort
toolkit/components/extensions/ExtensionParent.sys.mjs
modified
ProxyContextParent
toolkit/components/extensions/ExtensionParent.sys.mjs
modified
ExtensionPageContextParent
toolkit/components/extensions/ExtensionParent.sys.mjs
modified
BackgroundWorkerContextParent
toolkit/components/extensions/ExtensionParent.sys.mjs
modified
recvContextLoaded
toolkit/components/extensions/ExtensionParent.sys.mjs
modified
getContextById
toolkit/components/extensions/ExtensionParent.sys.mjs
modified
getContext
toolkit/components/extensions/ExtensionParent.sys.mjs
modified
getContextUnchecked
toolkit/components/extensions/ExtensionParent.sys.mjs
modified

Files Changed

  • toolkit/components/extensions/ExtensionParent.sys.mjs
diff --git a/toolkit/components/extensions/ExtensionParent.sys.mjs b/toolkit/components/extensions/ExtensionParent.sys.mjs
index 941a5353917..ddc2bc53b4b 100644
--- a/toolkit/components/extensions/ExtensionParent.sys.mjs
+++ b/toolkit/components/extensions/ExtensionParent.sys.mjs
@@ -62,19 +62,6 @@ schemaURLs.add("chrome://extensions/content/schemas/experiments.json");
 let GlobalManager;
 let ParentAPIManager;
 
-function verifyActorForContext(actor, context) {
-  if (JSWindowActorParent.isInstance(actor)) {
-    let target = actor.browsingContext.top.embedderElement;
-    if (context.parentMessageManager !== target.messageManager) {
-      throw new Error("Got message on unexpected message manager");
-    }
-  } else if (JSProcessActorParent.isInstance(actor)) {
-    if (actor.manager.remoteType !== context.extension.remoteType) {
-      throw new Error("Got message from unexpected process");
-    }
-  }
-}
-
 // This object loads the ext-*.js scripts that define the extension API.
 let apiManager = new (class extends SchemaAPIManager {
   constructor() {
@@ -267,7 +254,7 @@ const ProxyMessenger = {
   },
 
   openNative(nativeApp, sender) {
-    let context = ParentAPIManager.getContextById(sender.childId);
+    let context = ParentAPIManager.getContext(sender.childId, sender.actor);
     if (context.extension.hasPermission("geckoViewAddons")) {
       return new lazy.GeckoViewConnection(
         this.getSender(context.extension, sender),
@@ -477,30 +464,17 @@ const ProxyMessenger = {
   },
 
   trackNativeAppPort(port) {
-    if (!port?.native) {
-      return;
-    }
-
-    try {
-      let context = ParentAPIManager.getContextById(port.senderChildId);
+    if (port?.native) {
+      // The childId was verified against its actor in openNative().
+      let context = ParentAPIManager.getContextUnchecked(port.senderChildId);
       context?.trackNativeAppPort(port);
-    } catch {
-      // getContextById will throw if the context has been destroyed
-      // in the meantime.
     }
   },
 
   untrackNativeAppPort(port) {
-    if (!port?.native) {
-      return;
-    }
-
-    try {
-      let context = ParentAPIManager.getContextById(port.senderChildId);
+    if (port?.native) {
+      let context = ParentAPIManager.getContextUnchecked(port.senderChildId);
       context?.untrackNativeAppPort(port);
-    } catch {
-      // getContextById will throw if the context has been destroyed
-      // in the meantime.
     }
   },
 };
@@ -590,19 +564,21 @@ GlobalManager = {
  * parent side of a proxied API.
  */
 class ProxyContextParent extends BaseContext {
-  constructor(envType, extension, params, browsingContext, principal) {
+  constructor(envType, extension, params, actor, principal) {
     super(envType, extension);
 
     this.childId = params.childId;
     this.uri = Services.io.newURI(params.url);
-    this.browsingContext = browsingContext;
+    // The actor is tied to the WindowGlobal or DOMProcess.
+    this.actor = actor;
+    this.browsingContext = actor.browsingContext ?? null;
 
     this.incognito = params.incognito;
 
     this.listenerPromises = new Set();
 
     // browsingContext is null when subclassed by BackgroundWorkerContextParent.
-    const xulBrowser = browsingContext?.top.embedderElement;
+    const xulBrowser = this.browsingContext?.top.embedderElement;
     // This message manager is used by ParentAPIManager to send messages and to
     // close the ProxyContext if the underlying message manager closes. This
     // message manager object may change when `xulBrowser` swaps docshells, e.g.
@@ -834,11 +810,11 @@ class ContentScriptContextParent extends ProxyContextParent {}
  * ExtensionChild.sys.mjs.
  */
 class ExtensionPageContextParent extends ProxyContextParent {
-  constructor(envType, extension, params, browsingContext) {
-    super(envType, extension, params, browsingContext, extension.principal);
+  constructor(envType, extension, params, actor) {
+    super(envType, extension, params, actor, extension.principal);
 
     this.viewType = params.viewType;
-    this.isTopContext = browsingContext.top === browsingContext;
+    this.isTopContext = this.browsingContext.top === this.browsingContext;
 
     this.extension.views.add(this);
 
@@ -1042,11 +1018,12 @@ class DevToolsExtensionPageContextParent extends ExtensionPageContextParent {
  * worker script.
  */
 class BackgroundWorkerContextParent extends ProxyContextParent {
-  constructor(envType, extension, params) {
+  constructor(envType, extension, params, actor) {
     // TODO: split out from ProxyContextParent a base class that
     // doesn't expect a browsingContext and one for contexts that are
     // expected to have a browsingContext associated.
-    super(envType, extension, params, null, extension.principal);
+    // ProxyContextParent still reads actor.browsingContext.
+    super(envType, extension, params, actor, extension.principal);
 
     this.viewType = params.viewType;
     this.workerDescriptorId = params.workerDescriptorId;
@@ -1161,7 +1138,7 @@ ParentAPIManager = {
             envType,
             extension,
             data,
-            actor.browsingContext
+            actor
           );
           // TODO: make WindowContext a first-class member of BaseContext and
           // use it here. For now, just set innerWindowID (already declared on
@@ -1173,7 +1150,7 @@ ParentAPIManager = {
             envType,
             extension,
             data,
-            actor.browsingContext
+            actor
           );
         }
       } else if (JSProcessActorParent.isInstance(actor)) {
@@ -1193,7 +1170,12 @@ ParentAPIManager = {
             `Unexpected viewType ${data.viewType} on an extension process actor`
           );
         }
-        context = new BackgroundWorkerContextParent(envType, extension, data);
+        context = new BackgroundWorkerContextParent(
+          envType,
+          extension,
+          data,
+          actor
+        );
       } else {
         // Unreacheable: JSWindowActorParent and JSProcessActorParent are the
         // only actors.
@@ -1207,7 +1189,7 @@ ParentAPIManager = {
         envType,
         extension,
         data,
-        actor.browsingContext,
+        actor,
         principal
       );
     } else {
@@ -1217,8 +1199,7 @@ ParentAPIManager = {
   },
 
   recvContextLoaded(data, { actor }) {
-    let context = this.getContextById(data.childId);
-    verifyActorForContext(actor, context);
+    let context = this.getContext(data.childId, actor);
     const { extension } = context;
     extension.emit("extension-proxy-context-load:completed", context);
   },
@@ -1273,11 +1254,9 @@ ParentAPIManager = {
   },
 
   async recvAPICall(data, { actor }) {
-    let context = this.getContextById(data.childId);
+    let context = this.getContext(data.childId, actor);
     let target = actor.browsingContext?.top.embedderElement;
 
-    verifyActorForContext(actor, context);
-
     let reply = result => {
       if (target && !context.parentMessageManager) {
         Services.console.logStringMessage(
@@ -1352,9 +1331,7 @@ ParentAPIManager = {
   },
 
   async recvAddListener(data, { actor }) {
-    let context = this.getContextById(data.childId);
-
-    verifyActorForContext(actor, context);
+    let context = this.getContext(data.childId, actor);
 
     let { childId, alreadyLogged = false } = data;
     let handlingUserInput = false;
@@ -1432,8 +1409,8 @@ ParentAPIManager = {
     }
   },
 
-  async recvRemoveListener(data) {
-    let context = this.getContextById(data.childId);
+  async recvRemoveListener(data, { actor }) {
+    let context = this.getContext(data.childId, actor);
     let listener = context.listenerProxies.get(data.listenerId);
 
     let handler = await context.apiCan.asyncFindAPIPath(data.path);
@@ -1451,13 +1428,18 @@ ParentAPIManager = {
     }
   },
 
-  getContextById(childId) {
+  getContext(childId, actor) {
     let context = this.proxyContexts.get(childId);
-    if (!context) {
+    if (!context || context.actor !== actor) {
       throw new Error("WebExtension context not found!");
     }
     return context;
   },
+
+  // Only use when checking the actor is not needed.
+  getContextUnchecked(childId) {
+    return this.proxyContexts.get(childId);
+  },
 };
 
 ParentAPIManager.init();
@@ -2140,7 +2122,10 @@ async function promiseBackgroundViewLoaded(browser) {
   }
 
   if (childId) {
-    return ParentAPIManager.getContextById(childId);
+    let context = ParentAPIManager.getContextUnchecked(childId);
+    if (context && context.xulBrowser === browser) {
+      return context;
+    }
   }
 }
 
Loading diff…