CVE-2026-16404
Overview
Background
- GeckoView
- Mozilla’s embeddable web engine component that renders web content inside Firefox for Android and other Android apps.
- JSWindowActor
- a paired parent/child messaging abstraction (
GeckoViewPrompthere) that lets privileged parent-process code and content-process code exchange async messages across the process boundary. - `triggeringPrincipal`
- the security principal recorded as the initiator of a navigation, used to attribute and authorize a load.
- `BrowsingContext`
- the object representing a browsing context (tab/frame);
browsingContext.toprefers to the top-level frame of that tree.
Root Cause Analysis
The vulnerable path lived in PromptFactory inside GeckoViewPrompt.sys.mjs, where accepting a redirect prompt performed the navigation directly in content-reachable code via dwi.top.location.href = redirectURISpec. This assignment drove the top-level location without routing through the trusted parent process and without an explicit, correct triggering principal, so the navigation was performed in a context where the redirect could be attributed to the wrong origin and used to spoof the displayed content.
The fix moves the actual navigation into the parent process: the child now sends a GeckoView:UnblockRedirect message and the new GeckoViewPromptParent actor performs this.browsingContext.top.loadURI(uri, { triggeringPrincipal: sourceWG.documentPrincipal }). This restores the invariant that a top-level navigation is initiated by the parent with the source document’s principal as the explicit triggering principal, rather than by an implicit location.href write. Because loadURI is now driven from the parent with a correct principal, the redirect is properly attributed and can no longer be leveraged to spoof.
location.href assignment that lacked an explicit, correct triggering principal, allowing origin confusion; the fix relocates the navigation to a parent-process actor that calls loadURI with the source window global’s documentPrincipal as the triggering principal.Attack Path
- Trigger a redirect prompt
A malicious page causes GeckoView to present a redirect confirmation prompt whose
redirectURISpecpoints at content the attacker wants to display. - User accepts the prompt
When the user confirms, the old code executes
dwi.top.location.href = redirectURISpecin content-reachable context. - Mis-attributed navigation The top-level location changes without a properly asserted triggering principal, so the resulting load is attributed in a way that misrepresents its true origin.
- Spoofed presentation The attacker leverages the mis-attributed top-level navigation to spoof what the user believes they are viewing in Firefox for Android.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
receiveMessagemobile/shared/actors/GeckoViewPromptParent.sys.mjs |
modified | |
ifmobile/shared/actors/GeckoViewPromptParent.sys.mjs |
modified | |
unblockRedirectmobile/shared/actors/GeckoViewPromptParent.sys.mjs |
modified | |
ifmobile/shared/components/geckoview/GeckoViewPrompt.sys.mjs |
modified |
Files Changed
mobile/shared/actors/GeckoViewPromptParent.sys.mjsmobile/shared/actors/moz.buildmobile/shared/components/geckoview/GeckoViewPrompt.sys.mjsmobile/shared/components/geckoview/GeckoViewStartup.sys.mjs
Audit Directions
- Content-side top-level navigationFlag any
location.href/location.assignwrite totop(or another cross-context frame) performed in content-reachable code, and require it be routed through a parent actor with an explicittriggeringPrincipal. - Principal-less loadURIAudit
loadURI/fixupAndLoadURIStringcall sites for a missing or incorrecttriggeringPrincipal, which can cause origin mis-attribution and spoofing. - Prompt/redirect callbacksReview GeckoView prompt-response handlers that navigate on user acceptance to confirm the navigation is executed in the trusted parent process with the originating document’s principal.
Patch
diff --git a/mobile/shared/actors/GeckoViewPromptParent.sys.mjs b/mobile/shared/actors/GeckoViewPromptParent.sys.mjs
new file mode 100644
index 00000000000..9cc3eb684ad
--- /dev/null
+++ b/mobile/shared/actors/GeckoViewPromptParent.sys.mjs
@@ -0,0 +1,28 @@
+/* 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/. */
+
+import { GeckoViewActorParent } from "resource://gre/modules/GeckoViewActorParent.sys.mjs";
+
+export class GeckoViewPromptParent extends GeckoViewActorParent {
+ receiveMessage({ name, data }) {
+ if (name === "GeckoView:UnblockRedirect") {
+ this.unblockRedirect(data.redirectURISpec);
+ return null;
+ }
+
+ return super.receiveMessage({ name, data });
+ }
+
+ unblockRedirect(aRedirectURISpec) {
+ const sourceWG = this.browsingContext.currentWindowGlobal;
+ if (!sourceWG) {
+ return;
+ }
+
+ const uri = Services.io.newURI(aRedirectURISpec);
+ this.browsingContext.top.loadURI(uri, {
+ triggeringPrincipal: sourceWG.documentPrincipal,
+ });
+ }
+}
diff --git a/mobile/shared/actors/moz.build b/mobile/shared/actors/moz.build
index f9572acc3d1..56ed7942f65 100644
--- a/mobile/shared/actors/moz.build
+++ b/mobile/shared/actors/moz.build
@@ -23,6 +23,7 @@ FINAL_TARGET_FILES.actors += [
"GeckoViewPromptChild.sys.mjs",
"GeckoViewPrompterChild.sys.mjs",
"GeckoViewPrompterParent.sys.mjs",
+ "GeckoViewPromptParent.sys.mjs",
"GeckoViewPushParent.sys.mjs",
"GeckoViewSettingsChild.sys.mjs",
"LoadURIDelegateChild.sys.mjs",
diff --git a/mobile/shared/components/geckoview/GeckoViewPrompt.sys.mjs b/mobile/shared/components/geckoview/GeckoViewPrompt.sys.mjs
index e195b48ca75..82622affebf 100644
--- a/mobile/shared/components/geckoview/GeckoViewPrompt.sys.mjs
+++ b/mobile/shared/components/geckoview/GeckoViewPrompt.sys.mjs
@@ -345,7 +345,10 @@ export class PromptFactory {
},
({ response }) => {
if (response && dwi) {
- dwi.top.location.href = redirectURISpec;
+ const actor = dwi.windowGlobalChild.getActor("GeckoViewPrompt");
+ actor.sendAsyncMessage("GeckoView:UnblockRedirect", {
+ redirectURISpec,
+ });
}
}
);
diff --git a/mobile/shared/components/geckoview/GeckoViewStartup.sys.mjs b/mobile/shared/components/geckoview/GeckoViewStartup.sys.mjs
index 6996d0f77a2..9567abab0db 100644
--- a/mobile/shared/components/geckoview/GeckoViewStartup.sys.mjs
+++ b/mobile/shared/components/geckoview/GeckoViewStartup.sys.mjs
@@ -68,6 +68,9 @@ const JSWINDOWACTORS = {
includeChrome: true,
},
GeckoViewPrompt: {
+ parent: {
+ esModuleURI: "resource:///actors/GeckoViewPromptParent.sys.mjs",
+ },
child: {
esModuleURI: "resource:///actors/GeckoViewPromptChild.sys.mjs",
events: {