Firefox · DOM
CVE-2025-6434
Logic Error in DOM
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifbrowser/base/content/browser-siteIdentity.js |
modified | |
ifdom/security/test/https-first/browser_https_telemetry.js |
modified | |
fordom/security/test/https-only/browser_continue_button_delay.js |
modified | |
add_taskdom/security/test/https-only/browser_continue_button_delay.js |
modified |
Files Changed
browser/base/content/browser-siteIdentity.jsdom/security/test/https-first/browser_https_telemetry.jsdom/security/test/https-only/browser_continue_button_delay.jstoolkit/components/httpsonlyerror/content/errorpage.htmltoolkit/components/httpsonlyerror/content/errorpage.jstoolkit/components/httpsonlyerror/tests/browser/head.jstoolkit/themes/shared/aboutHttpsOnlyError.css
Patch
diff --git a/browser/base/content/browser-siteIdentity.js b/browser/base/content/browser-siteIdentity.js
index 71d085cc45c..def0375826a 100644
--- a/browser/base/content/browser-siteIdentity.js
+++ b/browser/base/content/browser-siteIdentity.js
@@ -617,6 +617,9 @@ var gIdentityHandler = {
if (this._popupInitialized) {
PanelMultiView.hidePopup(this._identityPopup);
}
+ // Ensure the browser is focused again, otherwise we may not trigger the
+ // security delay on a potential error page following this reload.
+ gBrowser.selectedBrowser.focus();
return;
}
// Otherwise we just refresh the interface
diff --git a/dom/security/test/https-first/browser_https_telemetry.js b/dom/security/test/https-first/browser_https_telemetry.js
index d952de1c94e..34242590e9e 100644
--- a/dom/security/test/https-first/browser_https_telemetry.js
+++ b/dom/security/test/https-first/browser_https_telemetry.js
@@ -375,7 +375,7 @@ add_task(async function () {
);
info("Waiting for openInsecureButton to be enabled.");
function callback() {
- if (!openInsecureButton.inert) {
+ if (!openInsecureButton.classList.contains("disabled")) {
observer.disconnect();
content.requestAnimationFrame(() => {
content.requestAnimationFrame(() => {
@@ -385,7 +385,7 @@ add_task(async function () {
}
}
const observer = new content.MutationObserver(callback);
- observer.observe(openInsecureButton, { attributeFilter: ["inert"] });
+ observer.observe(openInsecureButton, { attributeFilter: ["class"] });
callback();
});
diff --git a/dom/security/test/https-only/browser_continue_button_delay.js b/dom/security/test/https-only/browser_continue_button_delay.js
index 6bdee1610e0..a307687bde1 100644
--- a/dom/security/test/https-only/browser_continue_button_delay.js
+++ b/dom/security/test/https-only/browser_continue_button_delay.js
@@ -1,5 +1,21 @@
"use strict";
+// This test ensures the security delay (security.dialog_enable_delay) gets
+// properly applied to the "Continue" button on the HTTPS-Only error page. It
+// consists of the following checks:
+// 1. Check that the button gets enabled at the right time after a new tab is
+// loaded
+// 2. Check that the button gets disabled and re-enabled with the correct
+// timings on a focus loss due to a new tab being opened.
+// 3. Check that the button gets enabled with the correct timings when the
+// HTTPS-Only error page is loaded through the identity pane.
+
+// We specifically want a insecure url here that will fail to upgrade
+// eslint-disable-next-line @microsoft/sdl/no-insecure-url
+const TEST_URL = "http://untrusted.example.com";
+const TEST_PRINCIPAL =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(TEST_URL);
+
function waitForEnabledButton() {
return new Promise(resolve => {
const button = content.document.getElementById("openInsecure");
@@ -7,43 +23,28 @@ function waitForEnabledButton() {
for (const mutation of mutations) {
if (
mutation.type === "attributes" &&
- mutation.attributeName === "inert" &&
- !mutation.target.inert
+ mutation.attributeName === "class" &&
+ !mutation.target.classList.contains("disabled")
) {
resolve();
}
}
});
- observer.observe(button, { attributeFilter: ["inert"] });
+ observer.observe(button, { attributeFilter: ["class"] });
ok(
- button.inert,
- "The 'Continue to HTTP Site' button should be inert right after the error page is loaded."
+ button.classList.contains("disabled"),
+ "The 'Continue to HTTP Site' button should be disabled right after the error page is loaded/focused."
);
});
}
-add_task(async function () {
- waitForExplicitFinish();
-
- await SpecialPowers.pushPrefEnv({
- set: [["dom.security.https_only_mode", true]],
- });
-
- const specifiedDelay = Services.prefs.getIntPref(
- "security.dialog_enable_delay",
- 1000
- );
+const specifiedDelay = Services.prefs.getIntPref(
+ "security.dialog_enable_delay",
+ 1000
+);
- let loaded = BrowserTestUtils.waitForErrorPage(gBrowser.selectedBrowser);
- info("Loading insecure page");
+async function waitForEnabledButtonAndCheckTiming() {
const startTime = Date.now();
- BrowserTestUtils.startLoadingURIString(
- gBrowser,
- // We specifically want a insecure url here that will fail to upgrade
- // eslint-disable-next-line @microsoft/sdl/no-insecure-url
- "http://untrusted.example.com:80"
- );
- await loaded;
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], waitForEnabledButton);
const endTime = Date.now();
@@ -54,6 +55,49 @@ add_task(async function () {
specifiedDelay - 100,
`The observed delay (${observedDelay}ms) should be roughly the same or greater than the delay specified in "security.dialog_enable_delay" (${specifiedDelay}ms)`
);
+}
- finish();
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.security.https_only_mode", true]],
+ });
+
+ info("Loading insecure page");
+ let loaded = BrowserTestUtils.waitForErrorPage(gBrowser.selectedBrowser);
+ BrowserTestUtils.startLoadingURIString(gBrowser, TEST_URL);
+ await loaded;
+ await waitForEnabledButtonAndCheckTiming();
+
+ info("Opening and closing a new tab");
+ let newTab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ });
+ await BrowserTestUtils.removeTab(newTab);
+ await waitForEnabledButtonAndCheckTiming();
+
+ info("Loading page with exception");
+ await Services.perms.addFromPrincipal(
+ TEST_PRINCIPAL,
+ "https-only-load-insecure",
+ Ci.nsIHttpsOnlyModePermission.LOAD_INSECURE_ALLOW_SESSION
+ );
+ loaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ BrowserTestUtils.startLoadingURIString(gBrowser, TEST_URL);
+ await loaded;
+
+ info("Opening identity pane");
+ document.getElementById("identity-icon-box").click();
+ const identityPopup = document.getElementById("identity-popup");
+ ok(!!identityPopup, "Identity pane should exist");
+ await BrowserTestUtils.waitForPopupEvent(identityPopup, "shown");
+
+ info("Removing exception in identity pane");
+ const menulist = document.getElementById(
+ "identity-popup-security-httpsonlymode-menulist"
+ );
+ ok(!!menulist, "Identity pane should contain HTTPS-Only menulist");
+ loaded = BrowserTestUtils.waitForErrorPage(gBrowser.selectedBrowser);
+ menulist.getItemAtIndex(0).doCommand();
+ await loaded;
+ await waitForEnabledButtonAndCheckTiming();
});
diff --git a/toolkit/components/httpsonlyerror/content/errorpage.html b/toolkit/components/httpsonlyerror/content/errorpage.html
index 89e5e3cda37..1cfd46594a0 100644
--- a/toolkit/components/httpsonlyerror/content/errorpage.html
+++ b/toolkit/components/httpsonlyerror/content/errorpage.html
@@ -64,10 +64,12 @@
class="primary"
data-l10n-id="about-httpsonly-button-go-back"
></button>
+ <!-- This button only looks "disabled", but still allows JS events to
+ propagate for the clickjacking protection. -->
<button
id="openInsecure"
data-l10n-id="about-httpsonly-button-continue-to-site"
- inert
+ class="disabled"
></button>
</div>
diff --git a/toolkit/components/httpsonlyerror/content/errorpage.js b/toolkit/components/httpsonlyerror/content/errorpage.js
index 6b5fc931f48..7c7d5fd5dbd 100644
--- a/toolkit/components/httpsonlyerror/content/errorpage.js
+++ b/toolkit/components/httpsonlyerror/content/errorpage.js
@@ -7,6 +7,8 @@
"use strict";
const searchParams = new URLSearchParams(document.documentURI.split("?")[1]);
+const clickjackingDelay = RPMGetIntPref("security.dialog_enable_delay", 1000);
+let clickjackingTimeout;
function initPage() {
if (!searchParams.get("e")) {
@@ -49,10 +51,8 @@ function initPage() {
.getElementById("goBack")
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/dom/security/test/https-first/browser_https_telemetry.js b/dom/security/test/https-first/browser_https_telemetry.js
index d952de1c94e..34242590e9e 100644
--- a/dom/security/test/https-first/browser_https_telemetry.js
+++ b/dom/security/test/https-first/browser_https_telemetry.js
@@ -375,7 +375,7 @@ add_task(async function () {
);
info("Waiting for openInsecureButton to be enabled.");
function callback() {
- if (!openInsecureButton.inert) {
+ if (!openInsecureButton.classList.contains("disabled")) {
observer.disconnect();
content.requestAnimationFrame(() => {
content.requestAnimationFrame(() => {
@@ -385,7 +385,7 @@ add_task(async function () {
}
}
const observer = new content.MutationObserver(callback);
- observer.observe(openInsecureButton, { attributeFilter: ["inert"] });
+ observer.observe(openInsecureButton, { attributeFilter: ["class"] });
callback();
});
diff --git a/dom/security/test/https-only/browser_continue_button_delay.js b/dom/security/test/https-only/browser_continue_button_delay.js
index 6bdee1610e0..a307687bde1 100644
--- a/dom/security/test/https-only/browser_continue_button_delay.js
+++ b/dom/security/test/https-only/browser_continue_button_delay.js
@@ -1,5 +1,21 @@
"use strict";
+// This test ensures the security delay (security.dialog_enable_delay) gets
+// properly applied to the "Continue" button on the HTTPS-Only error page. It
+// consists of the following checks:
+// 1. Check that the button gets enabled at the right time after a new tab is
+// loaded
+// 2. Check that the button gets disabled and re-enabled with the correct
+// timings on a focus loss due to a new tab being opened.
+// 3. Check that the button gets enabled with the correct timings when the
+// HTTPS-Only error page is loaded through the identity pane.
+
+// We specifically want a insecure url here that will fail to upgrade
+// eslint-disable-next-line @microsoft/sdl/no-insecure-url
+const TEST_URL = "http://untrusted.example.com";
+const TEST_PRINCIPAL =
+ Services.scriptSecurityManager.createContentPrincipalFromOrigin(TEST_URL);
+
function waitForEnabledButton() {
return new Promise(resolve => {
const button = content.document.getElementById("openInsecure");
@@ -7,43 +23,28 @@ function waitForEnabledButton() {
for (const mutation of mutations) {
if (
mutation.type === "attributes" &&
- mutation.attributeName === "inert" &&
- !mutation.target.inert
+ mutation.attributeName === "class" &&
+ !mutation.target.classList.contains("disabled")
) {
resolve();
}
}
});
- observer.observe(button, { attributeFilter: ["inert"] });
+ observer.observe(button, { attributeFilter: ["class"] });
ok(
- button.inert,
- "The 'Continue to HTTP Site' button should be inert right after the error page is loaded."
+ button.classList.contains("disabled"),
+ "The 'Continue to HTTP Site' button should be disabled right after the error page is loaded/focused."
);
});
}
-add_task(async function () {
- waitForExplicitFinish();
-
- await SpecialPowers.pushPrefEnv({
- set: [["dom.security.https_only_mode", true]],
- });
-
- const specifiedDelay = Services.prefs.getIntPref(
- "security.dialog_enable_delay",
- 1000
- );
+const specifiedDelay = Services.prefs.getIntPref(
+ "security.dialog_enable_delay",
+ 1000
+);
- let loaded = BrowserTestUtils.waitForErrorPage(gBrowser.selectedBrowser);
- info("Loading insecure page");
+async function waitForEnabledButtonAndCheckTiming() {
const startTime = Date.now();
- BrowserTestUtils.startLoadingURIString(
- gBrowser,
- // We specifically want a insecure url here that will fail to upgrade
- // eslint-disable-next-line @microsoft/sdl/no-insecure-url
- "http://untrusted.example.com:80"
- );
- await loaded;
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], waitForEnabledButton);
const endTime = Date.now();
@@ -54,6 +55,49 @@ add_task(async function () {
specifiedDelay - 100,
`The observed delay (${observedDelay}ms) should be roughly the same or greater than the delay specified in "security.dialog_enable_delay" (${specifiedDelay}ms)`
);
+}
- finish();
+add_task(async function () {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.security.https_only_mode", true]],
+ });
+
+ info("Loading insecure page");
+ let loaded = BrowserTestUtils.waitForErrorPage(gBrowser.selectedBrowser);
+ BrowserTestUtils.startLoadingURIString(gBrowser, TEST_URL);
+ await loaded;
+ await waitForEnabledButtonAndCheckTiming();
+
+ info("Opening and closing a new tab");
+ let newTab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ });
+ await BrowserTestUtils.removeTab(newTab);
+ await waitForEnabledButtonAndCheckTiming();
+
+ info("Loading page with exception");
+ await Services.perms.addFromPrincipal(
+ TEST_PRINCIPAL,
+ "https-only-load-insecure",
+ Ci.nsIHttpsOnlyModePermission.LOAD_INSECURE_ALLOW_SESSION
+ );
+ loaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ BrowserTestUtils.startLoadingURIString(gBrowser, TEST_URL);
+ await loaded;
+
+ info("Opening identity pane");
+ document.getElementById("identity-icon-box").click();
+ const identityPopup = document.getElementById("identity-popup");
+ ok(!!identityPopup, "Identity pane should exist");
+ await BrowserTestUtils.waitForPopupEvent(identityPopup, "shown");
+
+ info("Removing exception in identity pane");
+ const menulist = document.getElementById(
+ "identity-popup-security-httpsonlymode-menulist"
+ );
+ ok(!!menulist, "Identity pane should contain HTTPS-Only menulist");
+ loaded = BrowserTestUtils.waitForErrorPage(gBrowser.selectedBrowser);
+ menulist.getItemAtIndex(0).doCommand();
+ await loaded;
+ await waitForEnabledButtonAndCheckTiming();
});
diff --git a/toolkit/components/httpsonlyerror/tests/browser/head.js b/toolkit/components/httpsonlyerror/tests/browser/head.js
index 7efbfbe8b50..ffe3c79df9e 100644
--- a/toolkit/components/httpsonlyerror/tests/browser/head.js
+++ b/toolkit/components/httpsonlyerror/tests/browser/head.js
@@ -81,7 +81,7 @@ function waitForAndClickOpenInsecureButton(browser) {
);
info("Waiting for openInsecureButton to be enabled.");
function callback() {
- if (!openInsecureButton.inert) {
+ if (!openInsecureButton.classList.contains("disabled")) {
info("openInsecureButton was enabled, waiting two frames.");
observer.disconnect();
content.requestAnimationFrame(() => {
@@ -93,7 +93,7 @@ function waitForAndClickOpenInsecureButton(browser) {
}
}
const observer = new content.MutationObserver(callback);
- observer.observe(openInsecureButton, { attributeFilter: ["inert"] });
+ observer.observe(openInsecureButton, { attributeFilter: ["class"] });
callback();
});
}
Loading diff…
References
On This Page