Low firefox Logic Error 🔧 Commit mapped

Overview

Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactlow
DescriptionMitigation bypass in the Bookmarks & History component
ComponentCore
Bug ClassLogic Error
Tracker2066019
Fix commit5bef4181fda7 (firefox) +40/-27
CISA KEVNot listed
CreditedRintaro Kawasugi
Disclosed2026-09-29

Changed Functions

FunctionChangeNotes
if
browser/components/places/content/controller.js
modified
registerCleanupFunction
browser/components/places/tests/browser/browser_toolbar_drop_bookmarklet.js
modified
for
browser/components/places/tests/browser/browser_toolbar_drop_bookmarklet.js
modified
for
browser/components/places/tests/browser/browser_toolbar_drop_multiple_with_bookmarklet.js
modified

Files Changed

  • browser/components/places/content/controller.js
  • browser/components/places/tests/browser/browser_toolbar_drop_bookmarklet.js
  • browser/components/places/tests/browser/browser_toolbar_drop_multiple_with_bookmarklet.js
diff --git a/browser/components/places/content/controller.js b/browser/components/places/content/controller.js
index 7773a850727..1faf824a5f1 100644
--- a/browser/components/places/content/controller.js
+++ b/browser/components/places/content/controller.js
@@ -1636,7 +1636,7 @@ var PlacesControllerDragHelper = {
         if (
           !flavor.startsWith("text/x-moz-place") &&
           (validNodes.length > 1 || dropCount > 1) &&
-          validNodes.some(n => n.uri?.startsWith("javascript:"))
+          validNodes.some(n => URL.parse(n.uri)?.protocol === "javascript:")
         ) {
           return false;
         }
@@ -1728,18 +1728,14 @@ var PlacesControllerDragHelper = {
     if (
       externalDrag &&
       (nodes.length > 1 || dropCount > 1) &&
-      nodes.some(n => n.uri?.startsWith("javascript:"))
+      nodes.some(n => URL.parse(n.uri)?.protocol === "javascript:")
     ) {
       throw new Error("Javascript bookmarklet passed with uris");
     }
 
     // If a single javascript url is being dropped from the urlbar or an external source,
     // show the bookmark dialog as a speedbump protection against malicious cases.
-    if (
-      nodes.length == 1 &&
-      externalDrag &&
-      nodes[0].uri?.startsWith("javascript")
-    ) {
+    if (nodes.length == 1 && externalDrag) {
       let uri;
       try {
         uri = Services.io.newURI(nodes[0].uri);
@@ -1747,7 +1743,7 @@ var PlacesControllerDragHelper = {
         // Invalid uri, we skip this code and the entry will be discarded later.
       }
 
-      if (uri) {
+      if (uri?.scheme === "javascript") {
         let bookmarkGuid = await PlacesUIUtils.showBookmarkDialog(
           {
             action: "add",
diff --git a/browser/components/places/tests/browser/browser_toolbar_drop_bookmarklet.js b/browser/components/places/tests/browser/browser_toolbar_drop_bookmarklet.js
index 91e089038b6..8b3e09be1a5 100644
--- a/browser/components/places/tests/browser/browser_toolbar_drop_bookmarklet.js
+++ b/browser/components/places/tests/browser/browser_toolbar_drop_bookmarklet.js
@@ -9,6 +9,7 @@ const sandbox = sinon.createSandbox();
 const URL1 = "https://example.com/1/";
 const URL2 = "https://example.com/2/";
 const BOOKMARKLET_URL = `javascript: (() => {alert('Hello, World!');})();`;
+const BOOKMARKLET_URL_MIXED_CASE = `JavaScript: (() => {})();`;
 let bookmarks;
 
 registerCleanupFunction(async function () {
@@ -34,12 +35,18 @@ add_task(async function test() {
   Assert.ok(placesItems, "PlacesToolbarItems should not be null");
 
   /**
-   * Simulates a drop of a bookmarklet URI onto the bookmarks bar.
+   * Simulates a drop of a bookmarklet URI onto the bookmarks bar and verifies
+   * the speedbump dialog appears.
    *
    * @param {string} aEffect
    *        The effect to use for the drop operation: move, copy, or link.
+   * @param {string} aBookmarkletUrl
+   *        The bookmarklet URL to be dropped onto the bookmarks bar.
    */
-  let simulateDragDrop = async function (aEffect) {
+  let simulateBookmarkletDragDrop = async function (
+    aEffect,
+    aBookmarkletUrl = BOOKMARKLET_URL
+  ) {
     info("Simulates drag/drop of a new javascript:URL to the bookmarks");
     await withBookmarksDialog(
       true,
@@ -47,7 +54,7 @@ add_task(async function test() {
         EventUtils.synthesizeDrop(
           toolbar,
           placesItems,
-          [[{ type: "text/x-moz-url", data: BOOKMARKLET_URL }]],
+          [[{ type: "text/x-moz-url", data: aBookmarkletUrl }]],
           aEffect,
           window
         );
@@ -61,11 +68,21 @@ add_task(async function test() {
 
         Assert.equal(
           location,
-          BOOKMARKLET_URL,
+          aBookmarkletUrl.trim().replace(/^javascript/i, "javascript"),
           "Should have opened the ShowBookmarksDialog with the correct bookmarklet url to be bookmarked"
         );
       }
     );
+  };
+
+  for (let effect of ["copy", "link"]) {
+    for (let bookmarkletUrl of [
+      BOOKMARKLET_URL,
+      BOOKMARKLET_URL_MIXED_CASE,
+      ` javascript: (() => {})();`,
+    ]) {
+      await simulateBookmarkletDragDrop(effect, bookmarkletUrl);
+    }
 
     info("Simulates drag/drop of a new URL to the bookmarks");
     let spy = sandbox
@@ -81,18 +98,13 @@ add_task(async function test() {
       toolbar,
       placesItems,
       [[{ type: "text/x-moz-url", data: URL1 }]],
-      aEffect,
+      effect,
       window
     );
 
     await promise;
     Assert.ok(spy.notCalled, "ShowBookmarksDialog on drop not called for url");
     sandbox.restore();
-  };
-
-  let effects = ["copy", "link"];
-  for (let effect of effects) {
-    await simulateDragDrop(effect);
   }
 
   info("Move of existing bookmark / bookmarklet on toolbar");
diff --git a/browser/components/places/tests/browser/browser_toolbar_drop_multiple_with_bookmarklet.js b/browser/components/places/tests/browser/browser_toolbar_drop_multiple_with_bookmarklet.js
index 729e456ca0a..c4ee0512c98 100644
--- a/browser/components/places/tests/browser/browser_toolbar_drop_multiple_with_bookmarklet.js
+++ b/browser/components/places/tests/browser/browser_toolbar_drop_multiple_with_bookmarklet.js
@@ -18,12 +18,12 @@ add_task(async function test() {
   // matter because we will set its data, effect, and mimeType manually.
   let placesItems = document.getElementById("PlacesToolbarItems");
   Assert.ok(placesItems, "PlacesToolbarItems should not be null");
-  let simulateDragDrop = async function (aEffect, aMimeType) {
-    let urls = [
-      "https://example.com/1/",
-      `javascript: (() => {alert('Hello, World!');})();`,
-      "https://example.com/2/",
-    ];
+  let simulateDragDrop = async function (
+    aEffect,
+    aMimeType,
+    aJsUrl = `javascript: (() => {alert('Hello, World!');})();`
+  ) {
+    let urls = ["https://example.com/1/", aJsUrl, "https://example.com/2/"];
 
     let data = urls.map(spec => spec + "\n" + spec).join("\n");
 
@@ -43,8 +43,13 @@ add_task(async function test() {
 
   // Simulate a bookmark drop for all of the mime types and effects.
   let mimeType = ["text/x-moz-url"];
-  let effects = ["copy", "link"];
-  for (let effect of effects) {
-    await simulateDragDrop(effect, mimeType);
+  for (let effect of ["copy", "link"]) {
+    for (let jsUrl of [
+      `javascript: (() => {alert('Hello, World!');})();`,
+      `JavaScript: (() => {})();`,
+      ` javascript: (() => {})();`,
+    ]) {
+      await simulateDragDrop(effect, mimeType, jsUrl);
+    }
   }
 });
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/browser/components/places/tests/browser/browser_toolbar_drop_bookmarklet.js b/browser/components/places/tests/browser/browser_toolbar_drop_bookmarklet.js
index 91e089038b6..8b3e09be1a5 100644
--- a/browser/components/places/tests/browser/browser_toolbar_drop_bookmarklet.js
+++ b/browser/components/places/tests/browser/browser_toolbar_drop_bookmarklet.js
@@ -9,6 +9,7 @@ const sandbox = sinon.createSandbox();
 const URL1 = "https://example.com/1/";
 const URL2 = "https://example.com/2/";
 const BOOKMARKLET_URL = `javascript: (() => {alert('Hello, World!');})();`;
+const BOOKMARKLET_URL_MIXED_CASE = `JavaScript: (() => {})();`;
 let bookmarks;
 
 registerCleanupFunction(async function () {
@@ -34,12 +35,18 @@ add_task(async function test() {
   Assert.ok(placesItems, "PlacesToolbarItems should not be null");
 
   /**
-   * Simulates a drop of a bookmarklet URI onto the bookmarks bar.
+   * Simulates a drop of a bookmarklet URI onto the bookmarks bar and verifies
+   * the speedbump dialog appears.
    *
    * @param {string} aEffect
    *        The effect to use for the drop operation: move, copy, or link.
+   * @param {string} aBookmarkletUrl
+   *        The bookmarklet URL to be dropped onto the bookmarks bar.
    */
-  let simulateDragDrop = async function (aEffect) {
+  let simulateBookmarkletDragDrop = async function (
+    aEffect,
+    aBookmarkletUrl = BOOKMARKLET_URL
+  ) {
     info("Simulates drag/drop of a new javascript:URL to the bookmarks");
     await withBookmarksDialog(
       true,
@@ -47,7 +54,7 @@ add_task(async function test() {
         EventUtils.synthesizeDrop(
           toolbar,
           placesItems,
-          [[{ type: "text/x-moz-url", data: BOOKMARKLET_URL }]],
+          [[{ type: "text/x-moz-url", data: aBookmarkletUrl }]],
           aEffect,
           window
         );
@@ -61,11 +68,21 @@ add_task(async function test() {
 
         Assert.equal(
           location,
-          BOOKMARKLET_URL,
+          aBookmarkletUrl.trim().replace(/^javascript/i, "javascript"),
           "Should have opened the ShowBookmarksDialog with the correct bookmarklet url to be bookmarked"
         );
       }
     );
+  };
+
+  for (let effect of ["copy", "link"]) {
+    for (let bookmarkletUrl of [
+      BOOKMARKLET_URL,
+      BOOKMARKLET_URL_MIXED_CASE,
+      ` javascript: (() => {})();`,
+    ]) {
+      await simulateBookmarkletDragDrop(effect, bookmarkletUrl);
+    }
 
     info("Simulates drag/drop of a new URL to the bookmarks");
     let spy = sandbox
@@ -81,18 +98,13 @@ add_task(async function test() {
       toolbar,
       placesItems,
       [[{ type: "text/x-moz-url", data: URL1 }]],
-      aEffect,
+      effect,
       window
     );
 
     await promise;
     Assert.ok(spy.notCalled, "ShowBookmarksDialog on drop not called for url");
     sandbox.restore();
-  };
-
-  let effects = ["copy", "link"];
-  for (let effect of effects) {
-    await simulateDragDrop(effect);
   }
 
   info("Move of existing bookmark / bookmarklet on toolbar");
diff --git a/browser/components/places/tests/browser/browser_toolbar_drop_multiple_with_bookmarklet.js b/browser/components/places/tests/browser/browser_toolbar_drop_multiple_with_bookmarklet.js
index 729e456ca0a..c4ee0512c98 100644
--- a/browser/components/places/tests/browser/browser_toolbar_drop_multiple_with_bookmarklet.js
+++ b/browser/components/places/tests/browser/browser_toolbar_drop_multiple_with_bookmarklet.js
@@ -18,12 +18,12 @@ add_task(async function test() {
   // matter because we will set its data, effect, and mimeType manually.
   let placesItems = document.getElementById("PlacesToolbarItems");
   Assert.ok(placesItems, "PlacesToolbarItems should not be null");
-  let simulateDragDrop = async function (aEffect, aMimeType) {
-    let urls = [
-      "https://example.com/1/",
-      `javascript: (() => {alert('Hello, World!');})();`,
-      "https://example.com/2/",
-    ];
+  let simulateDragDrop = async function (
+    aEffect,
+    aMimeType,
+    aJsUrl = `javascript: (() => {alert('Hello, World!');})();`
+  ) {
+    let urls = ["https://example.com/1/", aJsUrl, "https://example.com/2/"];
 
     let data = urls.map(spec => spec + "\n" + spec).join("\n");
 
@@ -43,8 +43,13 @@ add_task(async function test() {
 
   // Simulate a bookmark drop for all of the mime types and effects.
   let mimeType = ["text/x-moz-url"];
-  let effects = ["copy", "link"];
-  for (let effect of effects) {
-    await simulateDragDrop(effect, mimeType);
+  for (let effect of ["copy", "link"]) {
+    for (let jsUrl of [
+      `javascript: (() => {alert('Hello, World!');})();`,
+      `JavaScript: (() => {})();`,
+      ` javascript: (() => {})();`,
+    ]) {
+      await simulateDragDrop(effect, mimeType, jsUrl);
+    }
   }
 });
Loading diff…