CVE-2024-9391
Overview
Background
- Firefox Focus
- A privacy-focused Android browser (
focus-android) that shares the GeckoView/mozilla-componentsengine but ships a minimal, address-bar-light UI. - Fullscreen mode
- A web-triggered state (via the Fullscreen API) in which page content fills the viewport and browser chrome such as the address bar is hidden.
- `sessionUseCases.exitFullscreen()`
- A
mozilla-componentsuse case that commands the engine session to leave fullscreen and restore normal browser chrome. - `onBackPressed()`
- The
BrowserFragmentcallback invoked when the user presses the Android back button, expected to unwind transient UI states like popups and fullscreen.
Root Cause Analysis
When a page entered fullscreen mode in Firefox Focus, the only reliable way to leave it depended on UI paths that a maliciously crafted page could suppress, and the back-button handler onBackPressed() in BrowserFragment did not itself request an exit from fullscreen.
As a result, the invariant that a user-initiated navigation gesture (back) always returns the user to a state with visible, trustworthy chrome was violated: the code dismissed tabsPopup, hid trackingProtectionPanel, and cancelled siteNotSupportedSnackBarScope, but left the engine session in its fullscreen state. A page could therefore trap the user in fullscreen with no address bar, enabling it to spoof another site’s identity.
The fix adds requireComponents.sessionUseCases.exitFullscreen() to onBackPressed(), so the back gesture unconditionally instructs the engine to exit fullscreen and restore the chrome. Because the exit is now driven by the trusted app-side handler rather than page-controlled UI, the page can no longer prevent it.
onBackPressed() tore down auxiliary UI but never told the engine to leave fullscreen, so a page could keep the address bar hidden; the fix closes this by having the trusted back handler always call exitFullscreen().Attack Path
- Lure the user to a crafted page The attacker hosts a page in Firefox Focus that will programmatically request fullscreen.
- Enter fullscreen The page invokes the Fullscreen API so browser chrome, including the address bar, is hidden.
- Suppress normal exit The page manipulates its layout and event handling so the usual exit affordances are unavailable, leaving the back button as the user’s recourse.
- Back press fails to restore chrome
The user presses back, but
onBackPressed()never callsexitFullscreen(), so the session stays fullscreen and the address bar remains hidden. - Spoof another origin With no visible address bar, the page renders a fake UI impersonating a trusted site, deceiving the user about their true location.
Impact Assessment
Files Changed
mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/BrowserFragment.kt
Audit Directions
- Fullscreen exit pathsReview every navigation/dismissal handler (back, home, tab switch, app backgrounding) across fragments to confirm each one that unwinds UI also calls
exitFullscreen()when a session may be fullscreen. - Chrome-hiding statesLook for other states that hide the address bar or security indicators (custom tabs, PiP, immersive modes) and verify a page cannot make them sticky against user gestures.
- Trusted-vs-page controlAudit places where exiting a sensitive UI state depends on page-controlled events or elements rather than on the app’s own trusted handlers, since those are susceptible to suppression.
Patch
diff --git a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/BrowserFragment.kt b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/BrowserFragment.kt
index 8263a30b99c..accdb315dbb 100644
--- a/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/BrowserFragment.kt
+++ b/mobile/android/focus-android/app/src/main/java/org/mozilla/focus/fragment/BrowserFragment.kt
@@ -832,6 +832,7 @@ class BrowserFragment :
tabsPopup?.dismiss()
trackingProtectionPanel?.hide()
siteNotSupportedSnackBarScope?.cancel()
+ requireComponents.sessionUseCases.exitFullscreen()
}
override fun onHomePressed() = pictureInPictureFeature?.onHomePressed() ?: false