CVE-2025-1940
Overview
Background
- `PromptFeature`
- The Android Components (GeckoView/Fenix) component that renders web-triggered prompts (choice menus, alerts, etc.) over the browser UI.
- External-app launch confirmation
- When a page tries to open an external app via a custom URL scheme, Firefox shows a confirmation dialog (
SHOULD_OPEN_APP_LINK_PROMPT_DIALOG) so the user consents before leaving the browser. - `<select>` / choice prompts
- Web-content-controlled
SingleChoice/MultipleChoiceprompts (dropdown option lists) thatPromptFeaturerenders; their geometry is influenced by the page. - UI redress (CWE-1021)
- “Improper restriction of rendered UI layers”: one piece of UI overlapping another so the user misreads what they are confirming.
Root Cause Analysis
The fix is in PromptFeature.kt (mobile/android/android-components/.../feature/prompts/PromptFeature.kt). Before the patch, canShowThisPrompt would allow a content-driven choice prompt (SingleChoice, MultipleChoice, …) to be shown even while the external-app launch confirmation dialog was on screen. Because a page controls when/where it opens a <select> option list, it could pop a choice prompt that partially overlays the “launch external app?” confirmation, obscuring part of the confirmation text/buttons so the user approves launching an external application they did not intend to — the invariant “a web-content prompt must not be layered over the external-app confirmation” was not enforced.
The patch adds a new helper redirectDialogFragmentIsActive() that checks fragmentManager.findFragmentByTag("SHOULD_OPEN_APP_LINK_PROMPT_DIALOG") is a live DialogFragment, and inserts a guard at the top of canShowThisPrompt: if (redirectDialogFragmentIsActive()) return false. While the external-app redirect confirmation is active, no new content-driven prompt is permitted to display, so it cannot be used to obscure the confirmation. This is a layering/ordering enforcement fix, not a memory-safety change.
canShowThisPrompt did not suppress web-content choice prompts while the external-app launch confirmation was showing, so a page could overlay a <select> list to hide part of that confirmation; the fix refuses to show any prompt whenever the redirect confirmation dialog is active.Attack Path
- Attacker page Loaded in Firefox for Android, triggers a custom-scheme navigation that causes the external-app launch confirmation dialog to appear.
- Overlay prompt
Simultaneously opens a content-controlled choice/
<select>prompt positioned to overlap and partially obscure the confirmation’s text or buttons (inferred: exact positioning not shown by the diff, but the page controls the choice prompt). - User misreads The user, seeing an incomplete/benign-looking confirmation, taps to confirm.
- Primitive An external app is launched with the user’s unwitting consent (UI-redress / spoofing of the consent prompt). Android-only.
Impact Assessment
Files Changed
mobile/android/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt
Audit Directions
- Other modal confirmationsCheck whether permission, download, or security dialogs can likewise be overlaid by content-driven prompts, and whether
canShowThisPrompt/equivalent gates cover them. - Prompt layering/z-orderReview how choice prompts are positioned relative to system/confirmation dialogs; the fix suppresses rather than reorders, so confirm no path bypasses the suppression.
- Fragment-tag assumptionsVerify the
SHOULD_OPEN_APP_LINK_PROMPT_DIALOGtag is the only external-redirect confirmation tag; a renamed/variant dialog would evaderedirectDialogFragmentIsActive.
Patch
diff --git a/mobile/android/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt b/mobile/android/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt
index 96e225e6bab..fd0089ec647 100644
--- a/mobile/android/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt
+++ b/mobile/android/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt
@@ -9,6 +9,7 @@ import android.content.Intent
import android.net.Uri
import androidx.annotation.VisibleForTesting
import androidx.annotation.VisibleForTesting.Companion.PRIVATE
+import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import kotlinx.coroutines.CoroutineScope
@@ -1197,7 +1198,15 @@ class PromptFeature private constructor(
emitPromptDismissedFact(promptName = promptRequest::class.simpleName.ifNullOrEmpty { "" })
}
+ @VisibleForTesting
+ internal fun redirectDialogFragmentIsActive() =
+ (fragmentManager.findFragmentByTag("SHOULD_OPEN_APP_LINK_PROMPT_DIALOG") as? DialogFragment) != null
+
private fun canShowThisPrompt(promptRequest: PromptRequest): Boolean {
+ if (redirectDialogFragmentIsActive()) {
+ return false
+ }
+
return when (promptRequest) {
is SingleChoice,
is MultipleChoice,