CVE-2024-11697
Overview
Background
- `DownloadPrompter`
- the Toolkit helper object (in
DownloadUIHelper.sys.mjs) whoseprototypemethods display the download-related confirmation dialogs shown to the user. - `nsIPrompt.confirm`
- a synchronous prompt API that shows a two-button OK/Cancel dialog whose buttons are enabled and focusable the instant the dialog appears.
- `nsIPrompt.confirmEx`
- a richer prompt API that accepts a
flagsbitmask, letting the caller control button labels, layout, and timing behavior. - `BUTTON_DELAY_ENABLE`
- an
nsIPromptflag that disables the accept button for a short interval after the dialog opens, defeating queued or auto-repeating keystrokes.
Root Cause Analysis
The DownloadPrompter.prototype method that warns before launching an executable download rendered its “Open Executable File?” dialog through this._prompter.confirm(title, message), which presents the OK button as immediately actionable. Because the dialog inherited focus with no activation delay, a keypress (for example a held or scripted Enter/Space) that was already in flight or auto-repeating when the dialog appeared could dismiss it and confirm the launch before the user could read the warning. The security invariant — that a destructive confirmation must reflect a deliberate, informed user decision rather than a stray or attacker-timed keystroke — was therefore violated.
The fix replaces the call with this._prompter.confirmEx(title, message, flags, ...) and sets flags to Ci.nsIPrompt.BUTTON_DELAY_ENABLE | Ci.nsIPrompt.STD_OK_CANCEL_BUTTONS, so the accept button is briefly disabled after the dialog opens and pre-queued keystrokes can no longer auto-confirm it. It works because any keypress arriving during the enforced delay window is ignored by the button, forcing a fresh, post-delay interaction.
keypress could silently confirm it; adding BUTTON_DELAY_ENABLE via confirmEx closes that race by requiring input only after the button becomes live.Attack Path
- Prime the executable download The attacker gets the victim to download or open a file that Firefox treats as executable, so the “Open Executable File?” warning will be triggered.
- Induce a rapid keystroke The attacker arranges for the user to be holding or repeatedly pressing Enter/Space (via a game, a fake CAPTCHA, or scripted key auto-repeat) just as the dialog is about to appear.
- Win the timing race
The queued or auto-repeating
keypressreaches the freshly shownconfirmdialog whose OK button is already enabled and focused, activating it instantly. - Bypass the warning The dialog is dismissed as confirmed before the user perceives or reads it, and the executable launches without an informed decision.
Impact Assessment
Files Changed
toolkit/components/downloads/DownloadUIHelper.sys.mjs
Audit Directions
- Immediate-enable confirmationsSearch for other
nsIPrompt.confirm/confirmExcall sites guarding destructive or privileged actions that omitBUTTON_DELAY_ENABLE, since any of them can be defeated by keystroke-timing races. - Focus and default-button racesReview dialogs that open with a default accept button focused while input events may already be queued, especially those triggered by page-initiated flows like downloads or permission grants.
- Keypress-driven UI gatingAudit security-relevant UI that becomes actionable synchronously on appearance and can be reached by
keypress/auto-repeat, looking for missing debounce, delay, or explicit-interaction requirements.
Patch
diff --git a/toolkit/components/downloads/DownloadUIHelper.sys.mjs b/toolkit/components/downloads/DownloadUIHelper.sys.mjs
index 0d1d0e0a319..d4b903d3839 100644
--- a/toolkit/components/downloads/DownloadUIHelper.sys.mjs
+++ b/toolkit/components/downloads/DownloadUIHelper.sys.mjs
@@ -171,7 +171,10 @@ DownloadPrompter.prototype = {
"download-ui-file-executable-security-warning",
{ executable: PathUtils.filename(path) }
);
- return this._prompter.confirm(title, message);
+ let flags =
+ Ci.nsIPrompt.BUTTON_DELAY_ENABLE | Ci.nsIPrompt.STD_OK_CANCEL_BUTTONS;
+ let nulls = Array(4).fill(null);
+ return 0 == this._prompter.confirmEx(title, message, flags, ...nulls, {});
},
/**