Medium CVSS 7.1 firefox Spoofing 🔧 Commit mapped

Overview

Medium
Severity
7.1
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionA select option could partially obscure the confirmation prompt shown before launching external apps. This could be used to trick a user in to launching an external app unexpectedly. <br>*This issue only affects Android versions of Firefox.*
ComponentCore
Bug ClassSpoofing
Tracker1908488
Fix commit9cb8674152f2 (firefox) +9/-0
CWECWE-1021
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:N
CISA KEVNot listed
CreditedShaheen Fazim
Disclosed2025-03-04

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/MultipleChoice prompts (dropdown option lists) that PromptFeature renders; 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.

Key insight
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

  1. Attacker page Loaded in Firefox for Android, triggers a custom-scheme navigation that causes the external-app launch confirmation dialog to appear.
  2. 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).
  3. User misreads The user, seeing an incomplete/benign-looking confirmation, taps to confirm.
  4. Primitive An external app is launched with the user’s unwitting consent (UI-redress / spoofing of the consent prompt). Android-only.

Impact Assessment

This is an Android-only front-end UI-redress issue; it requires the user to interact with the obscured confirmation (CVSS vector AV:N, UI:R, C:H/I:L, moderate per Mozilla). There is no memory-safety primitive; the attacker gains the ability to trick the user into launching an external app unexpectedly, with impact bounded by what launching an arbitrary external app/intent can achieve on the device. It is not RCE within Firefox.

Files Changed

  • mobile/android/android-components/components/feature/prompts/src/main/java/mozilla/components/feature/prompts/PromptFeature.kt

Audit Directions

  • Other modal confirmations
    Check whether permission, download, or security dialogs can likewise be overlaid by content-driven prompts, and whether canShowThisPrompt/equivalent gates cover them.
  • Prompt layering/z-order
    Review 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 assumptions
    Verify the SHOULD_OPEN_APP_LINK_PROMPT_DIALOG tag is the only external-redirect confirmation tag; a renamed/variant dialog would evade redirectDialogFragmentIsActive.
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,
Loading diff…