Low firefox Logic Error 🔧 Commit mapped

Overview

Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactlow
DescriptionClickjacking issue in the Widget: Gtk component
ComponentWidget
Bug ClassLogic Error
Tracker2033848
Fix commitd07538533f1a (firefox) +11/-7
CISA KEVNot listed
CreditedAbdan Alkayyis
Disclosed2026-06-16

Background

Clickjacking
a UI-redress attack where a page tricks the user into performing an unintended interaction, such as confirming a native dialog.
`GtkFileChooser` dialog
the native GTK file-selection dialog Gecko raises for <input type="file"> uploads via nsFilePicker::OpenNonPortal.
GTK response ID
an integer returned when a dialog closes; GTK reserves negative values (like GTK_RESPONSE_ACCEPT = -3) for built-in semantics and treats non-negative values as application-defined.
Default widget
the button GTK activates when the user presses Enter, which for a dialog with a negative accept response is made the default automatically.

Root Cause Analysis

The vulnerable path was nsFilePicker::OpenNonPortal, which built the file chooser’s accept button with the GTK-reserved negative response ID GTK_RESPONSE_ACCEPT and additionally called gtk_dialog_set_default_response(..., GTK_RESPONSE_ACCEPT). Because GTK treats a negative accept response as a candidate for the dialog’s default widget, the accept button activated on Enter, violating the invariant that opening a file picker should require a deliberate, dialog-visible confirmation rather than a keypress that may already be held down. A malicious page could hold keyboard focus with Enter pressed just before the dialog appeared, so the queued keypress auto-confirmed a file selection the user never intentionally approved.

The fix introduces kFilePickerAccept = 0, a non-negative application-defined response ID, so GTK no longer promotes the accept button to the default widget, and it removes the explicit gtk_dialog_set_default_response call. nsFilePicker::DoneNonPortal is updated to treat kFilePickerAccept (and still GTK_RESPONSE_ACCEPT, which GTK can emit internally on a double-click) as a successful acceptance, preserving normal upload behavior while closing the auto-confirm window.

Key insight
The single mistake was using GTK’s reserved negative response ID GTK_RESPONSE_ACCEPT (plus an explicit default-response call), which made the accept button Enter-activatable; the fix switches to a non-negative kFilePickerAccept ID so the button is never treated as the default widget and cannot be triggered by a stray or pre-held Enter.

Attack Path

  1. Lure and focus A malicious page presents a benign-looking element and captures keyboard focus, priming the user to hold or repeatedly press Enter.
  2. Trigger the picker The page invokes a file-upload flow so Gecko calls nsFilePicker::OpenNonPortal, raising the native GtkFileChooser dialog.
  3. Inherit the held key Because the accept button carries GTK_RESPONSE_ACCEPT and is set as the default response, the still-pressed Enter immediately activates it.
  4. Auto-confirm upload The dialog closes with an accept response before the user perceives it, confirming whatever file was pre-selected or default-highlighted and delivering it to the page.

Impact Assessment

An attacker gains an unintended file upload by auto-confirming the native GTK file picker in the browser’s chrome/UI context on Linux, potentially exfiltrating a file the user did not knowingly choose. The severity is low and preconditions are significant: the user must be on a GTK/Linux build using the non-portal picker and must be induced to hold or spam Enter at the moment the dialog appears, with a selectable file already in view.

Changed Functions

FunctionChangeNotes
for
widget/gtk/nsFilePicker.cpp
modified
switch
widget/gtk/nsFilePicker.cpp
modified

Files Changed

  • widget/gtk/nsFilePicker.cpp

Audit Directions

  • Reserved response IDs as defaults
    audit every GTK dialog construction for GTK-reserved negative response IDs on accept/confirm actions that GTK may promote to the default widget, and prefer application-defined non-negative IDs.
  • Keypress-driven confirmation
    review native security- or privacy-sensitive dialogs (uploads, permission grants) for paths where a pre-held or queued Enter/activation can confirm before the user sees the dialog.
  • Response-handling completeness
    when a custom response ID replaces a built-in one, verify every consuming switch/handler (like DoneNonPortal) is updated to accept both the new ID and any IDs GTK can still emit internally.
diff --git a/widget/gtk/nsFilePicker.cpp b/widget/gtk/nsFilePicker.cpp
index bfe6b88ec0c..dde3f3d7833 100644
--- a/widget/gtk/nsFilePicker.cpp
+++ b/widget/gtk/nsFilePicker.cpp
@@ -52,6 +52,14 @@ using mozilla::dom::Promise;
 
 static nsIFile* sPrevDisplayDirectory = nullptr;
 
+// Use an application-defined response ID (non-negative) for the accept button
+// instead of GTK_RESPONSE_ACCEPT (-3). GTK's built-in negative response IDs
+// cause the button to be treated as the dialog's default widget, meaning it
+// activates on Enter. A non-negative ID prevents this, so a page that tricks
+// the user into holding Enter before the dialog appears cannot auto-confirm
+// an unintended file upload. See bug 2033848 and the equivalent Chrome fix.
+static const gint kFilePickerAccept = 0;
+
 void nsFilePicker::Shutdown() { NS_IF_RELEASE(sPrevDisplayDirectory); }
 
 #ifdef MOZ_ENABLE_DBUS
@@ -690,7 +698,7 @@ void nsFilePicker::OpenNonPortal() {
 
   GtkFileChooser* file_chooser = GTK_FILE_CHOOSER(gtk_file_chooser_dialog_new(
       title.get(), parent_widget, action, g_dgettext("gtk30", "_Cancel"),
-      GTK_RESPONSE_CANCEL, accept_button, GTK_RESPONSE_ACCEPT, nullptr));
+      GTK_RESPONSE_CANCEL, accept_button, kFilePickerAccept, nullptr));
 
   // If we have --enable-proxy-bypass-protection, then don't allow
   // remote URLs to be used.
@@ -766,11 +774,6 @@ void nsFilePicker::OpenNonPortal() {
     }
   }
 
-  if (GTK_IS_DIALOG(file_chooser)) {
-    gtk_dialog_set_default_response(GTK_DIALOG(file_chooser),
-                                    GTK_RESPONSE_ACCEPT);
-  }
-
   size_t count = mFilters.Length();
   for (size_t i = 0; i < count; ++i) {
     GtkFileFilter* filter = NewFilter(mFilters[i], mFilterNames[i]);
@@ -875,7 +878,8 @@ void nsFilePicker::DoneNonPortal(GtkWidget* file_chooser, gint response) {
   nsIFilePicker::ResultCode result;
   switch (response) {
     case GTK_RESPONSE_OK:
-    case GTK_RESPONSE_ACCEPT:
+    case GTK_RESPONSE_ACCEPT:  // emitted by GTK internally on double-click
+    case kFilePickerAccept:
       ReadValuesFromNonPortalFileChooser(GTK_FILE_CHOOSER(file_chooser));
       result = nsIFilePicker::returnOK;
       break;
Loading diff…