Firefox · Core
CVE-2026-8945
Sandbox Escape in Core
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
getMediaPermissionmobile/shared/actors/GeckoViewPermissionChild.sys.mjs |
modified | |
addCameraPermissionmobile/shared/actors/GeckoViewPermissionChild.sys.mjs |
modified | |
getAppPermissionsmobile/shared/actors/GeckoViewPermissionChild.sys.mjs |
modified | |
mediaRecordingStatusChangedmobile/shared/actors/GeckoViewPermissionChild.sys.mjs |
modified | |
checkIfGrantedByExtensionPermissionsmobile/shared/actors/GeckoViewPermissionChild.sys.mjs |
modified | |
ifmobile/shared/actors/GeckoViewPermissionChild.sys.mjs |
modified | |
switchmobile/shared/actors/GeckoViewPermissionParent.sys.mjs |
modified | |
constructormobile/shared/components/geckoview/GeckoViewPermission.sys.mjs |
modified | |
ifmobile/shared/components/geckoview/GeckoViewPermission.sys.mjs |
modified | |
checkIfGrantedByExtensionPermissionsmobile/shared/components/geckoview/GeckoViewPermission.sys.mjs |
modified |
Files Changed
mobile/shared/actors/GeckoViewPermissionChild.sys.mjsmobile/shared/actors/GeckoViewPermissionParent.sys.mjsmobile/shared/components/geckoview/GeckoViewPermission.sys.mjs
Patch
diff --git a/mobile/shared/actors/GeckoViewPermissionChild.sys.mjs b/mobile/shared/actors/GeckoViewPermissionChild.sys.mjs
index d454250bb56..6ca281f084f 100644
--- a/mobile/shared/actors/GeckoViewPermissionChild.sys.mjs
+++ b/mobile/shared/actors/GeckoViewPermissionChild.sys.mjs
@@ -4,20 +4,6 @@
import { GeckoViewActorChild } from "resource://gre/modules/GeckoViewActorChild.sys.mjs";
-const lazy = {};
-
-ChromeUtils.defineESModuleGetters(lazy, {
- E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs",
-});
-
-const PERM_ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION";
-
-const MAPPED_TO_EXTENSION_PERMISSIONS = [
- "persistent-storage",
- // TODO(Bug 1336194): support geolocation manifest permission
- // (see https://bugzilla.mozilla.org/show_bug.cgi?id=1336194#c17)l
-];
-
export class GeckoViewPermissionChild extends GeckoViewActorChild {
getMediaPermission(aPermission) {
return this.sendQuery("GeckoView:MediaPermission", {
@@ -26,11 +12,7 @@ export class GeckoViewPermissionChild extends GeckoViewActorChild {
}
addCameraPermission() {
- return this.sendQuery("AddCameraPermission");
- }
-
- getAppPermissions(aPermissions) {
- return this.sendQuery("GetAppPermissions", aPermissions);
+ return this.sendQuery("GeckoView:AddCameraPermission");
}
mediaRecordingStatusChanged(aDevices) {
@@ -38,146 +20,6 @@ export class GeckoViewPermissionChild extends GeckoViewActorChild {
devices: aDevices,
});
}
-
- // Some WebAPI permissions can be requested and granted to extensions through the
- // the extension manifest.json, which the user have been already prompted for
- // (e.g. at install time for the one listed in the manifest.json permissions property,
- // or at runtime through the optional_permissions property and the permissions.request
- // WebExtensions API method).
- //
- // WebAPI permission that are expected to be mapped to extensions permissions are listed
- // in the MAPPED_TO_EXTENSION_PERMISSIONS array.
- //
- // @param {nsIContentPermissionType} perm
- // The WebAPI permission being requested
- // @param {nsIContentPermissionRequest} aRequest
- // The nsIContentPermissionRequest as received by the promptPermission method.
- //
- // @returns {null | { allow: boolean, permission: Object }
- // Returns null if the request was not handled and should continue with the
- // regular permission prompting flow, otherwise it returns an object to
- // allow or disallow the permission request right away.
- checkIfGrantedByExtensionPermissions(perm, aRequest) {
- if (!aRequest.principal.addonPolicy) {
- // Not an extension, continue with the regular permission prompting flow.
- return null;
- }
-
- // Return earlier and continue with the regular permission prompting flow if the
- // the permission is no one that can be requested from the extension manifest file.
- if (!MAPPED_TO_EXTENSION_PERMISSIONS.includes(perm.type)) {
- return null;
- }
-
- // Disallow if the extension is not active anymore.
- if (!aRequest.principal.addonPolicy.active) {
- return { allow: false };
- }
-
- // Check if the permission have been already granted to the extension, if it is allow it right away.
- const isGranted =
- Services.perms.testPermissionFromPrincipal(
- aRequest.principal,
- perm.type
- ) === Services.perms.ALLOW_ACTION;
- if (isGranted) {
- return {
- allow: true,
- permission: { [perm.type]: Services.perms.ALLOW_ACTION },
- };
- }
-
- // continue with the regular permission prompting flow otherwise.
- return null;
- }
-
- async promptPermission(aRequest) {
- // Only allow exactly one permission request here.
- const types = aRequest.types.QueryInterface(Ci.nsIArray);
- if (types.length !== 1) {
- return { allow: false };
- }
-
- const perm = types.queryElementAt(0, Ci.nsIContentPermissionType);
-
- // Check if the request principal is an extension principal and if the permission requested
- // should be already granted based on the extension permissions (or disallowed right away
- // because the extension is not enabled anymore.
- const extensionResult = this.checkIfGrantedByExtensionPermissions(
- perm,
- aRequest
- );
- if (extensionResult) {
- return extensionResult;
- }
-
- if (
- perm.type === "desktop-notification" &&
- !aRequest.hasValidTransientUserGestureActivation &&
- Services.prefs.getBoolPref(
- "dom.webnotifications.requireuserinteraction",
- true
- )
- ) {
- // We need user interaction and don't have it.
- return { allow: false };
- }
-
- const principal =
- perm.type === "storage-access"
- ? aRequest.principal
- : aRequest.topLevelPrincipal;
-
- let allowOrDeny;
- try {
- allowOrDeny = await this.sendQuery("GeckoView:ContentPermission", {
- uri: principal.URI.displaySpec,
- thirdPartyOrigin: aRequest.principal.origin,
- principal: lazy.E10SUtils.serializePrincipal(principal),
- perm: perm.type,
- value: perm.capability,
- contextId: principal.originAttributes.geckoViewSessionContextId ?? null,
- privateMode: principal.privateBrowsingId != 0,
- });
-
- if (allowOrDeny === Services.perms.ALLOW_ACTION) {
- // Ask for app permission after asking for content permission.
- if (perm.type === "geolocation") {
- const granted = await this.getAppPermissions([
- PERM_ACCESS_FINE_LOCATION,
- ]);
- allowOrDeny = granted
- ? Services.perms.ALLOW_ACTION
- : Services.perms.DENY_ACTION;
- }
- }
- } catch (error) {
- console.error("Permission error:", error);
- allowOrDeny = Services.perms.DENY_ACTION;
- }
-
- // Manually release the target request here to facilitate garbage collection.
- aRequest = undefined;
-
- const allow = allowOrDeny === Services.perms.ALLOW_ACTION;
-
- // The storage access code adds itself to the perm manager; no need for us to do it.
- if (perm.type === "storage-access") {
- if (allow) {
- return { allow, permission: { "storage-access": "allow" } };
- }
- return { allow };
- }
-
- Services.perms.addFromPrincipal(
- principal,
- perm.type,
- allowOrDeny,
- Services.perms.EXPIRE_NEVER
- );
-
- return { allow };
- }
}
const { debug, warn } = GeckoViewPermissionChild.initLogging(
diff --git a/mobile/shared/actors/GeckoViewPermissionParent.sys.mjs b/mobile/shared/actors/GeckoViewPermissionParent.sys.mjs
index 3735721bd17..efd6d1e8dd9 100644
--- a/mobile/shared/actors/GeckoViewPermissionParent.sys.mjs
+++ b/mobile/shared/actors/GeckoViewPermissionParent.sys.mjs
@@ -50,10 +50,7 @@ export class GeckoViewPermissionParent extends GeckoViewActorParent {
debug`receiveMessage ${aMessage.name}`;
switch (aMessage.name) {
- case "GetAppPermissions": {
- return this.getAppPermissions(aMessage.data);
- }
- case "AddCameraPermission": {
+ case "GeckoView:AddCameraPermission": {
return this.addCameraPermission();
}
case "GeckoView:MediaPermission": {
@@ -68,12 +65,6 @@ export class GeckoViewPermissionParent extends GeckoViewActorParent {
aMessage.data
);
}
- case "GeckoView:ContentPermission": {
- return this.eventDispatcher.sendRequestForResult(
- "GeckoView:ContentPermission",
- aMessage.data
- );
- }
}
return super.receiveMessage(aMessage);
diff --git a/mobile/shared/components/geckoview/GeckoViewPermission.sys.mjs b/mobile/shared/components/geckoview/GeckoViewPermission.sys.mjs
index 51cc0f5588b..3e5107db427 100644
--- a/mobile/shared/components/geckoview/GeckoViewPermission.sys.mjs
+++ b/mobile/shared/components/geckoview/GeckoViewPermission.sys.mjs
@@ -4,18 +4,28 @@
import { GeckoViewUtils } from "resource://gre/modules/GeckoViewUtils.sys.mjs";
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs",
+ EventDispatcher: "resource://gre/modules/Messaging.sys.mjs",
+});
+
+const PERM_ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION";
+
+const MAPPED_TO_EXTENSION_PERMISSIONS = [
+ "persistent-storage",
+ // TODO(Bug 1336194): support geolocation manifest permission
+ // (see https://bugzilla.mozilla.org/show_bug.cgi?id=1336194#c17)l
+];
+
export class GeckoViewPermission {
constructor() {
this.wrappedJSObject = this;
}
async prompt(aRequest) {
- const window = aRequest.window
- ? aRequest.window
- : aRequest.element.ownerGlobal;
-
- const actor = window.windowGlobalChild.getActor("GeckoViewPermission");
- const result = await actor.promptPermission(aRequest);
+ const result = await this.promptPermission(aRequest);
if (!result.allow) {
aRequest.cancel();
} else {
@@ -24,6 +34,159 @@ export class GeckoViewPermission {
aRequest.allow(permission);
}
}
+
+ // Some WebAPI permissions can be requested and granted to extensions through the
+ // the extension manifest.json, which the user have been already prompted for
+ // (e.g. at install time for the one listed in the manifest.json permissions property,
+ // or at runtime through the optional_permissions property and the permissions.request
+ // WebExtensions API method).
+ //
+ // WebAPI permission that are expected to be mapped to extensions permissions are listed
+ // in the MAPPED_TO_EXTENSION_PERMISSIONS array.
+ //
+ // @param {nsIContentPermissionType} perm
+ // The WebAPI permission being requested
+ // @param {nsIContentPermissionRequest} aRequest
+ // The nsIContentPermissionRequest as received by the promptPermission method.
+ //
+ // @returns {null | { allow: boolean, permission: Object }
+ // Returns null if the request was not handled and should continue with the
+ // regular permission prompting flow, otherwise it returns an object to
+ // allow or disallow the permission request right away.
+ checkIfGrantedByExtensionPermissions(perm, aRequest) {
+ if (!aRequest.principal.addonPolicy) {
+ // Not an extension, continue with the regular permission prompting flow.
+ return null;
+ }
+
+ // Return earlier and continue with the regular permission prompting flow if the
+ // the permission is no one that can be requested from the extension manifest file.
+ if (!MAPPED_TO_EXTENSION_PERMISSIONS.includes(perm.type)) {
+ return null;
+ }
+
+ // Disallow if the extension is not active anymore.
+ if (!aRequest.principal.addonPolicy.active) {
+ return { allow: false };
+ }
+
+ // Check if the permission have been already granted to the extension, if it is allow it right away.
+ const isGranted =
+ Services.perms.testPermissionFromPrincipal(
+ aRequest.principal,
+ perm.type
+ ) === Services.perms.ALLOW_ACTION;
+ if (isGranted) {
+ return {
+ allow: true,
+ permission: { [perm.type]: Services.perms.ALLOW_ACTION },
+ };
+ }
+
+ // continue with the regular permission prompting flow otherwise.
+ return null;
+ }
+
+ async promptPermission(aRequest) {
+ // Only allow exactly one permission request here.
+ const types = aRequest.types.QueryInterface(Ci.nsIArray);
+ if (types.length !== 1) {
+ return { allow: false };
+ }
+
+ const perm = types.queryElementAt(0, Ci.nsIContentPermissionType);
+
+ // Check if the request principal is an extension principal and if the permission requested
+ // should be already granted based on the extension permissions (or disallowed right away
+ // because the extension is not enabled anymore.
+ const extensionResult = this.checkIfGrantedByExtensionPermissions(
+ perm,
+ aRequest
+ );
+ if (extensionResult) {
+ return extensionResult;
+ }
+
+ if (
+ perm.type === "desktop-notification" &&
+ !aRequest.hasValidTransientUserGestureActivation &&
+ Services.prefs.getBoolPref(
+ "dom.webnotifications.requireuserinteraction",
+ true
+ )
+ ) {
+ // We need user interaction and don't have it.
+ return { allow: false };
+ }
+
+ const principal =
+ perm.type === "storage-access"
+ ? aRequest.principal
+ : aRequest.topLevelPrincipal;
+
+ const window = aRequest.window
+ ? aRequest.window
+ : aRequest.element.ownerGlobal;
+
+ const windowEventDispatcher = lazy.EventDispatcher.for(window);
+
+ let allowOrDeny;
+ try {
+ allowOrDeny = await windowEventDispatcher.sendRequestForResult(
+ "GeckoView:ContentPermission",
+ {
+ uri: principal.URI.displaySpec,
+ thirdPartyOrigin: aRequest.principal.origin,
+ principal: lazy.E10SUtils.serializePrincipal(principal),
+ perm: perm.type,
+ value: perm.capability,
+ contextId:
+ principal.originAttributes.geckoViewSessionContextId ?? null,
+ privateMode: principal.privateBrowsingId != 0,
+ }
+ );
+
+ if (allowOrDeny === Services.perms.ALLOW_ACTION) {
+ // Ask for app permission after asking for content permission.
+ if (perm.type === "geolocation") {
+ const actor = window.browsingContext.currentWindowGlobal.getActor(
+ "GeckoViewPermission"
+ );
+ const granted = await actor.getAppPermissions([
+ PERM_ACCESS_FINE_LOCATION,
+ ]);
+ allowOrDeny = granted
+ ? Services.perms.ALLOW_ACTION
+ : Services.perms.DENY_ACTION;
+ }
+ }
+ } catch (error) {
+ console.error("Permission error:", error);
+ allowOrDeny = Services.perms.DENY_ACTION;
+ }
+
+ // Manually release the target request here to facilitate garbage collection.
+ aRequest = undefined;
+
+ const allow = allowOrDeny === Services.perms.ALLOW_ACTION;
+
+ // The storage access code adds itself to the perm manager; no need for us to do it.
+ if (perm.type === "storage-access") {
+ if (allow) {
+ return { allow, permission: { "storage-access": "allow" } };
+ }
+ return { allow };
+ }
... (file diff truncated)
Loading diff…
References
On This Page