Firefox · Toolkit
CVE-2026-12320
Logic Error in Toolkit
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
add_taskbrowser/components/aboutlogins/tests/browser/browser_osAuthDialog.js |
modified | |
iftoolkit/components/passwordmgr/LoginHelper.sys.mjs |
modified |
Files Changed
browser/components/aboutlogins/AboutLoginsParent.sys.mjsbrowser/components/aboutlogins/tests/browser/browser_osAuthDialog.jstoolkit/components/passwordmgr/LoginHelper.sys.mjstoolkit/components/satchel/megalist/MegalistViewModel.sys.mjstoolkit/components/satchel/megalist/aggregator/datasources/LoginDataSource.sys.mjs
Patch
diff --git a/browser/components/aboutlogins/AboutLoginsParent.sys.mjs b/browser/components/aboutlogins/AboutLoginsParent.sys.mjs
index fd1f392ed41..4ac658cacc9 100644
--- a/browser/components/aboutlogins/AboutLoginsParent.sys.mjs
+++ b/browser/components/aboutlogins/AboutLoginsParent.sys.mjs
@@ -276,7 +276,6 @@ export class AboutLoginsParent extends JSWindowActorParent {
let { isAuthorized, telemetryEvent } = await lazy.LoginHelper.requestReauth(
this.browsingContext.embedderElement,
- isOSAuthEnabled,
AboutLogins._authExpirationTime,
messageText.value,
captionText.value,
@@ -398,7 +397,6 @@ export class AboutLoginsParent extends JSWindowActorParent {
let reason = "export_logins";
let { isAuthorized, telemetryEvent } = await lazy.LoginHelper.requestReauth(
this.browsingContext.embedderElement,
- true,
null, // Prompt regardless of a recent prompt
messageText.value,
captionText.value,
diff --git a/browser/components/aboutlogins/tests/browser/browser_osAuthDialog.js b/browser/components/aboutlogins/tests/browser/browser_osAuthDialog.js
index 10e3ea794f0..07e027ffa92 100644
--- a/browser/components/aboutlogins/tests/browser/browser_osAuthDialog.js
+++ b/browser/components/aboutlogins/tests/browser/browser_osAuthDialog.js
@@ -234,6 +234,67 @@ add_task(async function test_os_auth_and_prp() {
LoginTestUtils.primaryPassword.disable();
});
+// When OS auth is disabled and a primary password exists but is already
+// unlocked, the primary password dialog should be shown.
+add_task(
+ async function test_prp_shown_when_os_auth_disabled_and_prp_unlocked() {
+ const osAuthWasEnabled = LoginHelper.getOSAuthEnabled();
+ LoginHelper.setOSAuthEnabled(false);
+
+ await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: "about:logins",
+ });
+
+ // Enable PrP after opening the tab so it doesn't prompt on load.
+ LoginTestUtils.primaryPassword.enable();
+ info("PrP has been enabled");
+
+ // First reveal: token is locked so PrP dialog must appear.
+ let mpDialogShown = forceAuthTimeoutAndWaitForMPDialog("authenticate");
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () {
+ let loginItem = content.document.querySelector("login-item");
+ loginItem.shadowRoot.querySelector(".reveal-password-checkbox").click();
+ });
+ await mpDialogShown;
+ info("PrP dialog shown and authenticated");
+
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () {
+ let revealCheckbox = content.document
+ .querySelector("login-item")
+ .shadowRoot.querySelector(".reveal-password-checkbox");
+ Assert.ok(revealCheckbox.checked, "password revealed after PrP auth");
+ revealCheckbox.click();
+ Assert.ok(!revealCheckbox.checked, "password hidden again");
+ });
+
+ // PrP is now unlocked and OS auth is disabled. The second reveal should
+ // prompt for PrP again.
+ mpDialogShown = forceAuthTimeoutAndWaitForMPDialog("authenticate");
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () {
+ let loginItem = content.document.querySelector("login-item");
+ info("Clicking reveal, expecting PrP dialog (OS auth is disabled)");
+ loginItem.shadowRoot.querySelector(".reveal-password-checkbox").click();
+ });
+ await mpDialogShown;
+ info("PrP dialog shown again as expected when OS auth is disabled");
+
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () {
+ let revealCheckbox = content.document
+ .querySelector("login-item")
+ .shadowRoot.querySelector(".reveal-password-checkbox");
+ Assert.ok(
+ revealCheckbox.checked,
+ "reveal checkbox should be checked after PrP auth"
+ );
+ });
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ LoginTestUtils.primaryPassword.disable();
+ LoginHelper.setOSAuthEnabled(osAuthWasEnabled);
+ }
+);
+
add_task(async function test_osAuth_shown_on_copy_password() {
if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
Assert.ok(
diff --git a/toolkit/components/passwordmgr/LoginHelper.sys.mjs b/toolkit/components/passwordmgr/LoginHelper.sys.mjs
index 8542feb5d34..91d496b46ca 100644
--- a/toolkit/components/passwordmgr/LoginHelper.sys.mjs
+++ b/toolkit/components/passwordmgr/LoginHelper.sys.mjs
@@ -1639,7 +1639,6 @@ export const LoginHelper = {
*
* @param {Element} browser
* The <browser> that the prompt should be shown on
- * @param OSReauthEnabled Boolean indicating if OS reauth should be tried
* @param expirationTime Optional timestamp indicating next required re-authentication
* @param messageText Formatted and localized string to be displayed when the OS auth dialog is used.
* @param captionText Formatted and localized string to be displayed when the OS auth dialog is used.
@@ -1647,7 +1646,6 @@ export const LoginHelper = {
*/
async requestReauth(
browser,
- OSReauthEnabled,
expirationTime,
messageText,
captionText,
@@ -1676,12 +1674,16 @@ export const LoginHelper = {
};
}
+ let isOSAuthEnabled = this.getOSAuthEnabled();
+
// Default to true if there is no primary password and OS reauth is not available
- if (!token.hasPassword && !OSReauthEnabled) {
+ if (!token.hasPassword && !isOSAuthEnabled) {
isAuthorized = true;
telemetryEvent = {
name: "reauthenticateOsAuth",
- value: "success_disabled",
+ value: lazy.OSKeyStore.canReauth()
+ ? "success_disabled"
+ : "success_unsupported_platform",
};
return {
isAuthorized,
@@ -1689,8 +1691,8 @@ export const LoginHelper = {
};
}
// Use the OS auth dialog if there is no primary password
- // or if primary password is already unlocked.
- if ((!token.hasPassword || token.isLoggedIn()) && OSReauthEnabled) {
+ // or if primary password is already unlocked and os auth is enabled.
+ if (isOSAuthEnabled && (!token.hasPassword || token.isLoggedIn())) {
let result;
try {
isAuthorized = await this.verifyUserOSAuth(
diff --git a/toolkit/components/satchel/megalist/MegalistViewModel.sys.mjs b/toolkit/components/satchel/megalist/MegalistViewModel.sys.mjs
index 07f233f480c..d4af6fc6588 100644
--- a/toolkit/components/satchel/megalist/MegalistViewModel.sys.mjs
+++ b/toolkit/components/satchel/megalist/MegalistViewModel.sys.mjs
@@ -222,12 +222,10 @@ export class MegalistViewModel {
ReauthPrimaryPassword: "reauth_cpm",
};
const reason = reasonMap[command.id];
- const osAuthForPw = lazy.LoginHelper.getOSAuthEnabled();
const { isAuthorized } = await lazy.LoginHelper.requestReauth(
lazy.BrowserWindowTracker.getTopWindow({
allowFromInactiveWorkspace: true,
}).gBrowser,
- osAuthForPw,
this.#authExpirationTime,
command.OSAuthPromptMessage,
command.OSAuthCaptionMessage,
diff --git a/toolkit/components/satchel/megalist/aggregator/datasources/LoginDataSource.sys.mjs b/toolkit/components/satchel/megalist/aggregator/datasources/LoginDataSource.sys.mjs
index bc1600fd838..cef10aaa586 100644
--- a/toolkit/components/satchel/megalist/aggregator/datasources/LoginDataSource.sys.mjs
+++ b/toolkit/components/satchel/megalist/aggregator/datasources/LoginDataSource.sys.mjs
@@ -561,12 +561,9 @@ export class LoginDataSource extends DataSourceBase {
allowFromInactiveWorkspace: true,
}).browsingContext;
- const isOSAuthEnabled = LoginHelper.getOSAuthEnabled();
-
const reason = "export_cpm";
let { isAuthorized, telemetryEvent } = await LoginHelper.requestReauth(
browsingContext,
- isOSAuthEnabled,
null, // Prompt regardless of a recent prompt
this.#exportPasswordsStrings.OSReauthMessage,
this.#exportPasswordsStrings.OSAuthDialogCaption,
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/browser/components/aboutlogins/tests/browser/browser_osAuthDialog.js b/browser/components/aboutlogins/tests/browser/browser_osAuthDialog.js
index 10e3ea794f0..07e027ffa92 100644
--- a/browser/components/aboutlogins/tests/browser/browser_osAuthDialog.js
+++ b/browser/components/aboutlogins/tests/browser/browser_osAuthDialog.js
@@ -234,6 +234,67 @@ add_task(async function test_os_auth_and_prp() {
LoginTestUtils.primaryPassword.disable();
});
+// When OS auth is disabled and a primary password exists but is already
+// unlocked, the primary password dialog should be shown.
+add_task(
+ async function test_prp_shown_when_os_auth_disabled_and_prp_unlocked() {
+ const osAuthWasEnabled = LoginHelper.getOSAuthEnabled();
+ LoginHelper.setOSAuthEnabled(false);
+
+ await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ url: "about:logins",
+ });
+
+ // Enable PrP after opening the tab so it doesn't prompt on load.
+ LoginTestUtils.primaryPassword.enable();
+ info("PrP has been enabled");
+
+ // First reveal: token is locked so PrP dialog must appear.
+ let mpDialogShown = forceAuthTimeoutAndWaitForMPDialog("authenticate");
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () {
+ let loginItem = content.document.querySelector("login-item");
+ loginItem.shadowRoot.querySelector(".reveal-password-checkbox").click();
+ });
+ await mpDialogShown;
+ info("PrP dialog shown and authenticated");
+
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () {
+ let revealCheckbox = content.document
+ .querySelector("login-item")
+ .shadowRoot.querySelector(".reveal-password-checkbox");
+ Assert.ok(revealCheckbox.checked, "password revealed after PrP auth");
+ revealCheckbox.click();
+ Assert.ok(!revealCheckbox.checked, "password hidden again");
+ });
+
+ // PrP is now unlocked and OS auth is disabled. The second reveal should
+ // prompt for PrP again.
+ mpDialogShown = forceAuthTimeoutAndWaitForMPDialog("authenticate");
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () {
+ let loginItem = content.document.querySelector("login-item");
+ info("Clicking reveal, expecting PrP dialog (OS auth is disabled)");
+ loginItem.shadowRoot.querySelector(".reveal-password-checkbox").click();
+ });
+ await mpDialogShown;
+ info("PrP dialog shown again as expected when OS auth is disabled");
+
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () {
+ let revealCheckbox = content.document
+ .querySelector("login-item")
+ .shadowRoot.querySelector(".reveal-password-checkbox");
+ Assert.ok(
+ revealCheckbox.checked,
+ "reveal checkbox should be checked after PrP auth"
+ );
+ });
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ LoginTestUtils.primaryPassword.disable();
+ LoginHelper.setOSAuthEnabled(osAuthWasEnabled);
+ }
+);
+
add_task(async function test_osAuth_shown_on_copy_password() {
if (!OSKeyStoreTestUtils.canTestOSKeyStoreLogin()) {
Assert.ok(
Loading diff…
References
On This Page