High CVSS 3.9 firefox Spoofing 🔧 Commit mapped

Overview

High
Severity
3.9
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionAndroid apps can load web pages using the Custom Tabs feature. This feature supports a transition animation that could have been used to trick a user into granting sensitive permissions by hiding what the user was actually clicking.
ComponentCore
Bug ClassSpoofing
Tracker1928334
Fix commit28d54b6f230d (firefox) +17/-1
CWECWE-359
CVSS vectorCVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:L/I:L/A:N
CISA KEVNot listed
CreditedPhilipp Beer
Disclosed2025-03-04

Background

Custom Tabs
An Android feature that lets a third-party app open web content inside a host browser’s UI. In Firefox for Android (Fenix) this is served by ExternalAppBrowserActivity, a subclass of HomeActivity.
`dispatchTouchEvent`
The Android Activity entry point through which every touch is routed before it reaches a view. Returning true consumes the event so no child view acts on it.
Enter animation
When an activity launches it plays a transition animation. onEnterAnimationComplete() is the system callback fired once that animation has finished and the window is fully, stably on screen.
Tapjacking / UI redress
Delivering taps to UI that is still moving, partially transparent, or mid-transition, so the user believes they are touching something other than what actually receives the tap (e.g. a permission grant).

Root Cause Analysis

The fix adds a touch gate to ExternalAppBrowserActivity (mobile/android/fenix/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivity.kt). Before the patch, the Custom Tab activity inherited HomeActivity.dispatchTouchEvent, which processed touches immediately — including during the activity’s launch/enter transition animation. Because a launching Custom Tab can be animated in (slide/fade), a malicious calling app controlling the transition could present attacker-chosen pixels while the real, touch-sensitive UI (for example a permission or sensitive-action control) was already live underneath, so a tap landed on the browser control the user could not clearly see. The invariant being violated is “do not deliver touches to the Custom Tab UI until the window is visually settled.”

The patch introduces a var isFinishedAnimating = false flag and overrides dispatchTouchEvent: while !isFinishedAnimating it returns true, swallowing every touch. The flag is flipped to true only in the newly overridden onEnterAnimationComplete(). To make this override possible, HomeActivity.dispatchTouchEvent was changed from final override to plain override (HomeActivity.kt) so the subclass can extend it. Net effect: no touch is honored until the enter animation has genuinely completed, closing the window in which transition animations could be used to hide what the user is clicking.

Key insight
The Custom Tab activity accepted and acted on touch input during its entry transition, letting a caller-controlled animation visually mask the control a tap would actually hit — a classic tapjacking window that the fix closes by discarding all touches until onEnterAnimationComplete().

Attack Path

  1. Malicious Android app Installs on the device and invokes Firefox’s Custom Tabs to load a page, controlling the launch/transition animation parameters.
  2. Animation cover During the enter animation the Custom Tab UI is already interactive, but the transition presents misleading pixels over the real controls (inferred: exact overlaid control depends on what sensitive prompt is reachable in that window).
  3. User tap The user, believing they are tapping benign content, taps where a sensitive browser control (e.g. a permission grant) actually sits.
  4. Primitive The tap is delivered to the hidden control, granting a sensitive permission or triggering a sensitive action without informed consent (UI redress / tapjacking).

Impact Assessment

This is an Android-only UI-redress (tapjacking) issue in the Fenix front end, requiring local presence of a malicious app and user interaction (UI:R in the CVSS vector). There is no memory-safety primitive; the gain is tricking the user into authorizing a sensitive action/permission they did not intend. Severity is bounded by what a single mis-aimed tap can authorize and by the short duration of the enter animation; it is not RCE. The advisory tags CWE-359 (private information exposure), reflecting that the tricked grant can expose sensitive permissions, but the diff substantiates a tapjacking/clickjacking mechanism rather than a direct data leak.

Changed Functions

FunctionChangeNotes
if
mobile/android/fenix/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivity.kt
modified

Files Changed

  • mobile/android/fenix/app/src/main/java/org/mozilla/fenix/HomeActivity.kt
  • mobile/android/fenix/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivity.kt

Audit Directions

  • Other animated activities
    Review any Activity/Fragment that becomes interactive before its enter transition completes, especially ones that can be launched by third-party apps, for the same early-touch exposure.
  • Sensitive prompts under Custom Tabs
    Enumerate which permission/sensitive controls can appear in a Custom Tab early in its lifecycle; those are the real targets of this class of tapjacking.
  • `final` removals
    Confirm no other HomeActivity subclass relied on the previously-final dispatchTouchEvent, and that no subclass now forgets to call through.
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/HomeActivity.kt
index cf5c73b4f90..15c41299aa1 100644
--- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/HomeActivity.kt
+++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/HomeActivity.kt
@@ -972,7 +972,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
         return false
     }
 
-    final override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
+    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
         ProfilerMarkers.addForDispatchTouchEvent(components.core.engine.profiler, ev)
         return super.dispatchTouchEvent(ev)
     }
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivity.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivity.kt
index 774c0525a36..452f3dcbbc9 100644
--- a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivity.kt
+++ b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/customtabs/ExternalAppBrowserActivity.kt
@@ -7,6 +7,7 @@ package org.mozilla.fenix.customtabs
 import android.app.assist.AssistContent
 import android.net.Uri
 import android.os.Build
+import android.view.MotionEvent
 import androidx.annotation.RequiresApi
 import androidx.annotation.VisibleForTesting
 import mozilla.components.browser.state.selector.findCustomTab
@@ -24,6 +25,8 @@ const val EXTRA_IS_SANDBOX_CUSTOM_TAB = "org.mozilla.fenix.customtabs.EXTRA_IS_S
  */
 @Suppress("TooManyFunctions")
 open class ExternalAppBrowserActivity : HomeActivity() {
+    var isFinishedAnimating = false
+
     override fun onResume() {
         super.onResume()
 
@@ -74,4 +77,17 @@ open class ExternalAppBrowserActivity : HomeActivity() {
         val currentTabUrl = getExternalTab()?.content?.url
         outContent?.webUri = currentTabUrl?.let { Uri.parse(it) }
     }
+
+    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
+        if (!isFinishedAnimating) {
+            return true
+        }
+
+        return super.dispatchTouchEvent(ev)
+    }
+
+    override fun onEnterAnimationComplete() {
+        super.onEnterAnimationComplete()
+        isFinishedAnimating = true
+    }
 }
Loading diff…