Medium firefox Logic Error 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionSpoofing issue in the WebAuthn component in Firefox for Android
ComponentCore
Bug ClassLogic Error
Tracker1988145
Fix commit8cf3589f064d (firefox) +36/-14
CISA KEVNot listed
Creditedhafiizh & kang ali
Disclosed2026-02-24

Changed Functions

FunctionChangeNotes
AlwaysDeniedSchemes
mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AlwaysDeniedSchemes.kt
modified
AppLinksInterceptor
mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AppLinksInterceptor.kt
modified
AppLinksUseCases
mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AppLinksUseCases.kt
modified

Files Changed

  • mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AlwaysDeniedSchemes.kt
  • mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AppLinksInterceptor.kt
  • mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AppLinksUseCases.kt
  • mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksInterceptorTest.kt
  • mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksUseCasesTest.kt
diff --git a/mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AlwaysDeniedSchemes.kt b/mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AlwaysDeniedSchemes.kt
new file mode 100644
index 00000000000..9584e720897
--- /dev/null
+++ b/mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AlwaysDeniedSchemes.kt
@@ -0,0 +1,22 @@
+/* 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/. */
+
+package mozilla.components.feature.app.links
+
+import java.util.Locale
+
+/**
+ * Utility class whether scheme is allowed or denied.
+ */
+class AlwaysDeniedSchemes(private val schemes: Set<String>) {
+    /**
+     * Whether or not we should deny given URI scheme by dangerous etc.
+     *
+     * @param scheme A scheme of URI
+     * @return `true` If scheme should be denied
+     */
+    fun shouldDeny(scheme: String?): Boolean {
+         return schemes.contains(scheme?.lowercase(Locale.ROOT))
+    }
+}
diff --git a/mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AppLinksInterceptor.kt b/mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AppLinksInterceptor.kt
index 96512c2e113..35160e4d497 100644
--- a/mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AppLinksInterceptor.kt
+++ b/mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AppLinksInterceptor.kt
@@ -60,7 +60,7 @@ private val ALLOWED_SCHEMES_IN_SUBFRAME: List<String> = listOf(
 class AppLinksInterceptor(
     private val context: Context,
     private val engineSupportedSchemes: Set<String> = ENGINE_SUPPORTED_SCHEMES,
-    private val alwaysDeniedSchemes: Set<String> = ALWAYS_DENY_SCHEMES,
+    private val alwaysDeniedSchemes: AlwaysDeniedSchemes = AlwaysDeniedSchemes(ALWAYS_DENY_SCHEMES),
     private var launchInApp: () -> Boolean = { false },
     private val useCases: AppLinksUseCases = AppLinksUseCases(
         context,
@@ -102,7 +102,7 @@ class AppLinksInterceptor(
             // If scheme not in supported list then follow user preference
             !launchInApp() && !isPossibleAuthentication(tabSessionState) && engineSupportsScheme -> true
             // Never go to an external app when scheme is in blocklist
-            alwaysDeniedSchemes.contains(uriScheme) -> true
+            alwaysDeniedSchemes.shouldDeny(uriScheme) -> true
             else -> false
         }
 
diff --git a/mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AppLinksUseCases.kt b/mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AppLinksUseCases.kt
index 8b136f3f1ed..b851ed978f2 100644
--- a/mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AppLinksUseCases.kt
+++ b/mobile/android/android-components/components/feature/app-links/src/main/java/mozilla/components/feature/app/links/AppLinksUseCases.kt
@@ -52,7 +52,7 @@ private const val ANDROID_RESOLVER_PACKAGE_NAME = "android"
 class AppLinksUseCases(
     private val context: Context,
     private var launchInApp: () -> Boolean = { false },
-    private val alwaysDeniedSchemes: Set<String> = ALWAYS_DENY_SCHEMES,
+    private val alwaysDeniedSchemes: AlwaysDeniedSchemes = AlwaysDeniedSchemes(ALWAYS_DENY_SCHEMES),
     private val installedBrowsers: Browsers = BrowsersCache.all(context),
 ) {
     @Suppress(
@@ -159,7 +159,7 @@ class AppLinksUseCases(
 
             val appIntent = when {
                 intent?.data == null -> null
-                alwaysDeniedSchemes.contains(intent.data?.scheme) -> null
+                alwaysDeniedSchemes.shouldDeny(intent.data?.scheme) -> null
                 else -> intent
             }
 
@@ -249,7 +249,7 @@ class AppLinksUseCases(
             appIntent?.let {
                 try {
                     val scheme = appIntent.data?.scheme
-                    if (scheme != null && alwaysDeniedSchemes.contains(scheme)) {
+                    if (alwaysDeniedSchemes.shouldDeny(scheme)) {
                         return
                     }
 
diff --git a/mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksInterceptorTest.kt b/mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksInterceptorTest.kt
index eb0b5cfb7b3..d3e1aa66794 100644
--- a/mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksInterceptorTest.kt
+++ b/mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksInterceptorTest.kt
@@ -202,7 +202,7 @@ class AppLinksInterceptorTest {
         val blocklistedScheme = "blocklisted"
         val feature = AppLinksInterceptor(
             context = mockContext,
-            alwaysDeniedSchemes = setOf(blocklistedScheme),
+            alwaysDeniedSchemes = AlwaysDeniedSchemes(setOf(blocklistedScheme)),
             launchInApp = { true },
             useCases = mockUseCases,
         )
@@ -259,7 +259,7 @@ class AppLinksInterceptorTest {
         val feature = AppLinksInterceptor(
             context = mockContext,
             engineSupportedSchemes = setOf(supportedScheme),
-            alwaysDeniedSchemes = setOf(blocklistedScheme),
+            alwaysDeniedSchemes = AlwaysDeniedSchemes(setOf(blocklistedScheme)),
             launchInApp = { false },
             useCases = mockUseCases,
         )
@@ -279,7 +279,7 @@ class AppLinksInterceptorTest {
         val feature = AppLinksInterceptor(
             context = mockContext,
             engineSupportedSchemes = setOf(supportedScheme),
-            alwaysDeniedSchemes = setOf(notSupportedScheme),
+            alwaysDeniedSchemes = AlwaysDeniedSchemes(setOf(notSupportedScheme)),
             launchInApp = { false },
             useCases = mockUseCases,
         )
@@ -300,7 +300,7 @@ class AppLinksInterceptorTest {
         val feature = AppLinksInterceptor(
             context = mockContext,
             engineSupportedSchemes = setOf(supportedScheme),
-            alwaysDeniedSchemes = setOf(blocklistedScheme),
+            alwaysDeniedSchemes = AlwaysDeniedSchemes(setOf(blocklistedScheme)),
             launchInApp = { true },
             useCases = mockUseCases,
         )
@@ -321,7 +321,7 @@ class AppLinksInterceptorTest {
         val feature = AppLinksInterceptor(
             context = mockContext,
             engineSupportedSchemes = setOf(supportedScheme),
-            alwaysDeniedSchemes = setOf(blocklistedScheme),
+            alwaysDeniedSchemes = AlwaysDeniedSchemes(setOf(blocklistedScheme)),
             launchInApp = { false },
             useCases = mockUseCases,
         )
@@ -343,7 +343,7 @@ class AppLinksInterceptorTest {
         val feature = AppLinksInterceptor(
             context = mockContext,
             engineSupportedSchemes = setOf(supportedScheme),
-            alwaysDeniedSchemes = setOf(blocklistedScheme),
+            alwaysDeniedSchemes = AlwaysDeniedSchemes(setOf(blocklistedScheme)),
             launchInApp = { false },
             useCases = mockUseCases,
         )
@@ -594,7 +594,7 @@ class AppLinksInterceptorTest {
         val feature = AppLinksInterceptor(
             context = mockContext,
             engineSupportedSchemes = setOf(supportedScheme),
-            alwaysDeniedSchemes = setOf(blocklistedScheme),
+            alwaysDeniedSchemes = AlwaysDeniedSchemes(setOf(blocklistedScheme)),
             launchInApp = { true },
             useCases = mockUseCases,
         )
@@ -616,7 +616,7 @@ class AppLinksInterceptorTest {
         val feature = AppLinksInterceptor(
             context = mockContext,
             engineSupportedSchemes = setOf(supportedScheme),
-            alwaysDeniedSchemes = setOf(blocklistedScheme),
+            alwaysDeniedSchemes = AlwaysDeniedSchemes(setOf(blocklistedScheme)),
             launchInApp = { false },
             useCases = mockUseCases,
         )
diff --git a/mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksUseCasesTest.kt b/mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksUseCasesTest.kt
index 9614cf363e0..a4de61e4d4a 100644
--- a/mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksUseCasesTest.kt
+++ b/mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksUseCasesTest.kt
@@ -357,7 +357,7 @@ class AppLinksUseCasesTest {
     fun `A intent scheme denied should return no app intent`() {
         val uri = "intent://details/#Intent"
         val context = createContext(Triple(uri, appPackage, ""))
-        val subject = AppLinksUseCases(context, { true }, alwaysDeniedSchemes = setOf("intent"))
+        val subject = AppLinksUseCases(context, { true }, alwaysDeniedSchemes = AlwaysDeniedSchemes(setOf("intent")))
 
         val redirect = subject.interceptedAppLinkRedirect.invoke(uri)
 
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksInterceptorTest.kt b/mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksInterceptorTest.kt
index eb0b5cfb7b3..d3e1aa66794 100644
--- a/mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksInterceptorTest.kt
+++ b/mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksInterceptorTest.kt
@@ -202,7 +202,7 @@ class AppLinksInterceptorTest {
         val blocklistedScheme = "blocklisted"
         val feature = AppLinksInterceptor(
             context = mockContext,
-            alwaysDeniedSchemes = setOf(blocklistedScheme),
+            alwaysDeniedSchemes = AlwaysDeniedSchemes(setOf(blocklistedScheme)),
             launchInApp = { true },
             useCases = mockUseCases,
         )
@@ -259,7 +259,7 @@ class AppLinksInterceptorTest {
         val feature = AppLinksInterceptor(
             context = mockContext,
             engineSupportedSchemes = setOf(supportedScheme),
-            alwaysDeniedSchemes = setOf(blocklistedScheme),
+            alwaysDeniedSchemes = AlwaysDeniedSchemes(setOf(blocklistedScheme)),
             launchInApp = { false },
             useCases = mockUseCases,
         )
@@ -279,7 +279,7 @@ class AppLinksInterceptorTest {
         val feature = AppLinksInterceptor(
             context = mockContext,
             engineSupportedSchemes = setOf(supportedScheme),
-            alwaysDeniedSchemes = setOf(notSupportedScheme),
+            alwaysDeniedSchemes = AlwaysDeniedSchemes(setOf(notSupportedScheme)),
             launchInApp = { false },
             useCases = mockUseCases,
         )
@@ -300,7 +300,7 @@ class AppLinksInterceptorTest {
         val feature = AppLinksInterceptor(
             context = mockContext,
             engineSupportedSchemes = setOf(supportedScheme),
-            alwaysDeniedSchemes = setOf(blocklistedScheme),
+            alwaysDeniedSchemes = AlwaysDeniedSchemes(setOf(blocklistedScheme)),
             launchInApp = { true },
             useCases = mockUseCases,
         )
@@ -321,7 +321,7 @@ class AppLinksInterceptorTest {
         val feature = AppLinksInterceptor(
             context = mockContext,
             engineSupportedSchemes = setOf(supportedScheme),
-            alwaysDeniedSchemes = setOf(blocklistedScheme),
+            alwaysDeniedSchemes = AlwaysDeniedSchemes(setOf(blocklistedScheme)),
             launchInApp = { false },
             useCases = mockUseCases,
         )
@@ -343,7 +343,7 @@ class AppLinksInterceptorTest {
         val feature = AppLinksInterceptor(
             context = mockContext,
             engineSupportedSchemes = setOf(supportedScheme),
-            alwaysDeniedSchemes = setOf(blocklistedScheme),
+            alwaysDeniedSchemes = AlwaysDeniedSchemes(setOf(blocklistedScheme)),
             launchInApp = { false },
             useCases = mockUseCases,
         )
@@ -594,7 +594,7 @@ class AppLinksInterceptorTest {
         val feature = AppLinksInterceptor(
             context = mockContext,
             engineSupportedSchemes = setOf(supportedScheme),
-            alwaysDeniedSchemes = setOf(blocklistedScheme),
+            alwaysDeniedSchemes = AlwaysDeniedSchemes(setOf(blocklistedScheme)),
             launchInApp = { true },
             useCases = mockUseCases,
         )
@@ -616,7 +616,7 @@ class AppLinksInterceptorTest {
         val feature = AppLinksInterceptor(
             context = mockContext,
             engineSupportedSchemes = setOf(supportedScheme),
-            alwaysDeniedSchemes = setOf(blocklistedScheme),
+            alwaysDeniedSchemes = AlwaysDeniedSchemes(setOf(blocklistedScheme)),
             launchInApp = { false },
             useCases = mockUseCases,
         )
diff --git a/mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksUseCasesTest.kt b/mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksUseCasesTest.kt
index 9614cf363e0..a4de61e4d4a 100644
--- a/mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksUseCasesTest.kt
+++ b/mobile/android/android-components/components/feature/app-links/src/test/java/mozilla/components/feature/app/links/AppLinksUseCasesTest.kt
@@ -357,7 +357,7 @@ class AppLinksUseCasesTest {
     fun `A intent scheme denied should return no app intent`() {
         val uri = "intent://details/#Intent"
         val context = createContext(Triple(uri, appPackage, ""))
-        val subject = AppLinksUseCases(context, { true }, alwaysDeniedSchemes = setOf("intent"))
+        val subject = AppLinksUseCases(context, { true }, alwaysDeniedSchemes = AlwaysDeniedSchemes(setOf("intent")))
 
         val redirect = subject.interceptedAppLinkRedirect.invoke(uri)
Loading diff…