Medium firefox Logic Error 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionMalicious pages could use Firefox for Android to pass FIDO: links to the OS and trigger the "hybrid" passkey transport. An attacker within Bluetooth range could have used this to trick the user into using their passkey to log the attacker's computer into the target account.
ComponentCore
Bug ClassLogic Error
Tracker1922357
Fix commitbff9465f2eae (firefox) +28/-2
CISA KEVNot listed
CreditedMasterSplinter
Disclosed2025-03-04

Files Changed

  • mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngineSession.kt
  • mobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineSessionTest.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/AppLinksUseCasesTest.kt
  • mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/IntentUtils.java
  • mobile/android/geckoview/src/test/java/org/mozilla/gecko/util/IntentUtilsTest.java
diff --git a/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngineSession.kt b/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngineSession.kt
index 0980c997796..2e2898ffb9e 100644
--- a/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngineSession.kt
+++ b/mobile/android/android-components/components/browser/engine-gecko/src/main/java/mozilla/components/browser/engine/gecko/GeckoEngineSession.kt
@@ -1553,7 +1553,7 @@ class GeckoEngineSession(
         internal const val ABOUT_BLANK = "about:blank"
         internal const val JS_SCHEME = "javascript"
         internal val BLOCKED_SCHEMES =
-            listOf("file", "resource", JS_SCHEME) // See 1684761 and 1684947
+            listOf("file", "resource", "fido", JS_SCHEME) // See 1684761 and 1684947
 
         /**
          * Provides an ErrorType corresponding to the error code provided.
diff --git a/mobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineSessionTest.kt b/mobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineSessionTest.kt
index f5b121169d4..ef7087a4cdd 100644
--- a/mobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineSessionTest.kt
+++ b/mobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineSessionTest.kt
@@ -637,6 +637,11 @@ class GeckoEngineSessionTest {
         engineSession.loadUrl("RESOURCE://package/test.text")
         verify(geckoSession, never()).load(GeckoSession.Loader().uri("resource://package/test.text"))
         verify(geckoSession, never()).load(GeckoSession.Loader().uri("RESOURCE://package/test.text"))
+
+        engineSession.loadUrl("fido:/12345678")
+        engineSession.loadUrl("FIDO:/12345678")
+        verify(geckoSession, never()).load(GeckoSession.Loader().uri("fido:/12345678"))
+        verify(geckoSession, never()).load(GeckoSession.Loader().uri("FIDO:/12345678"))
     }
 
     @Test
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 be9c48c9d7c..5fc4e22ba5f 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
@@ -325,6 +325,7 @@ class AppLinksUseCases(
             "https", "moz-extension", "moz-safe-about", "resource", "view-source", "ws", "wss", "blob",
         )
 
-        internal val ALWAYS_DENY_SCHEMES: Set<String> = setOf("jar", "file", "javascript", "data", "about", "content")
+        internal val ALWAYS_DENY_SCHEMES: Set<String> =
+            setOf("jar", "file", "javascript", "data", "about", "content", "fido")
     }
 }
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 a03057e069f..a43351739d7 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
@@ -47,6 +47,7 @@ class AppLinksUseCasesTest {
     private val javascriptUrl = "javascript:'hello, world'"
     private val jarUrl = "jar:file://some/path/test.html"
     private val contentUrl = "content://media/external_primary/downloads/12345"
+    private val fidoPath = "fido:12345678"
     private val fileType = "audio/mpeg"
     private val layerUrl = "https://example.com"
     private val layerPackage = "com.example.app"
@@ -217,6 +218,15 @@ class AppLinksUseCasesTest {
         assertFalse(redirect.isRedirect())
     }
 
+    @Test
+    fun `A fido url is not an app link`() {
+        val context = createContext(Triple(fidoPath, appPackage, ""))
+        val subject = AppLinksUseCases(context, { true })
+
+        val redirect = subject.interceptedAppLinkRedirect(fidoPath)
+        assertFalse(redirect.isRedirect())
+    }
+
     @Test
     fun `Will not redirect app link if browser option set to false and scheme is supported`() {
         val context = createContext(Triple(appUrl, appPackage, ""))
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/IntentUtils.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/IntentUtils.java
index 36fde18a02e..ff582dfe33b 100644
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/IntentUtils.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/IntentUtils.java
@@ -76,6 +76,10 @@ public class IntentUtils {
       return getSafeIntent(aUri) != null;
     }
 
+    if ("fido".equals(scheme)) {
+      return false;
+    }
+
     return true;
   }
 
diff --git a/mobile/android/geckoview/src/test/java/org/mozilla/gecko/util/IntentUtilsTest.java b/mobile/android/geckoview/src/test/java/org/mozilla/gecko/util/IntentUtilsTest.java
index 24315ff585c..6f9d0b405e1 100644
--- a/mobile/android/geckoview/src/test/java/org/mozilla/gecko/util/IntentUtilsTest.java
+++ b/mobile/android/geckoview/src/test/java/org/mozilla/gecko/util/IntentUtilsTest.java
@@ -63,4 +63,10 @@ public class IntentUtilsTest {
     final String uri = "intent:non_scheme_intent#Intent;end";
     assertTrue(IntentUtils.isUriSafeForScheme(uri));
   }
+
+  @Test
+  public void unsafeFidoUri() {
+    final String uri = "fido:/12345678";
+    assertFalse(IntentUtils.isUriSafeForScheme(uri));
+  }
 }
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/mobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineSessionTest.kt b/mobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineSessionTest.kt
index f5b121169d4..ef7087a4cdd 100644
--- a/mobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineSessionTest.kt
+++ b/mobile/android/android-components/components/browser/engine-gecko/src/test/java/mozilla/components/browser/engine/gecko/GeckoEngineSessionTest.kt
@@ -637,6 +637,11 @@ class GeckoEngineSessionTest {
         engineSession.loadUrl("RESOURCE://package/test.text")
         verify(geckoSession, never()).load(GeckoSession.Loader().uri("resource://package/test.text"))
         verify(geckoSession, never()).load(GeckoSession.Loader().uri("RESOURCE://package/test.text"))
+
+        engineSession.loadUrl("fido:/12345678")
+        engineSession.loadUrl("FIDO:/12345678")
+        verify(geckoSession, never()).load(GeckoSession.Loader().uri("fido:/12345678"))
+        verify(geckoSession, never()).load(GeckoSession.Loader().uri("FIDO:/12345678"))
     }
 
     @Test
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 a03057e069f..a43351739d7 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
@@ -47,6 +47,7 @@ class AppLinksUseCasesTest {
     private val javascriptUrl = "javascript:'hello, world'"
     private val jarUrl = "jar:file://some/path/test.html"
     private val contentUrl = "content://media/external_primary/downloads/12345"
+    private val fidoPath = "fido:12345678"
     private val fileType = "audio/mpeg"
     private val layerUrl = "https://example.com"
     private val layerPackage = "com.example.app"
@@ -217,6 +218,15 @@ class AppLinksUseCasesTest {
         assertFalse(redirect.isRedirect())
     }
 
+    @Test
+    fun `A fido url is not an app link`() {
+        val context = createContext(Triple(fidoPath, appPackage, ""))
+        val subject = AppLinksUseCases(context, { true })
+
+        val redirect = subject.interceptedAppLinkRedirect(fidoPath)
+        assertFalse(redirect.isRedirect())
+    }
+
     @Test
     fun `Will not redirect app link if browser option set to false and scheme is supported`() {
         val context = createContext(Triple(appUrl, appPackage, ""))
diff --git a/mobile/android/geckoview/src/test/java/org/mozilla/gecko/util/IntentUtilsTest.java b/mobile/android/geckoview/src/test/java/org/mozilla/gecko/util/IntentUtilsTest.java
index 24315ff585c..6f9d0b405e1 100644
--- a/mobile/android/geckoview/src/test/java/org/mozilla/gecko/util/IntentUtilsTest.java
+++ b/mobile/android/geckoview/src/test/java/org/mozilla/gecko/util/IntentUtilsTest.java
@@ -63,4 +63,10 @@ public class IntentUtilsTest {
     final String uri = "intent:non_scheme_intent#Intent;end";
     assertTrue(IntentUtils.isUriSafeForScheme(uri));
   }
+
+  @Test
+  public void unsafeFidoUri() {
+    final String uri = "fido:/12345678";
+    assertFalse(IntentUtils.isUriSafeForScheme(uri));
+  }
 }
Loading diff…