Medium firefox Logic Error 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionClickjacking issue, information disclosure in the PDF Viewer component
ComponentToolkit
Bug ClassLogic Error
Tracker2006500
Fix commit65a8d9226edf (firefox) +76/-0
CISA KEVNot listed
CreditedLyra Rebane
Disclosed2026-01-13

Changed Functions

FunctionChangeNotes
add_task
toolkit/components/pdfjs/test/browser_pdfjs_response_link.js
modified

Files Changed

  • toolkit/components/pdfjs/content/PdfStreamConverter.sys.mjs
  • toolkit/components/pdfjs/test/browser.toml
  • toolkit/components/pdfjs/test/browser_pdfjs_response_link.js
  • toolkit/components/pdfjs/test/pdf_response_link.sjs
diff --git a/toolkit/components/pdfjs/content/PdfStreamConverter.sys.mjs b/toolkit/components/pdfjs/content/PdfStreamConverter.sys.mjs
index 7a05366416c..fb4d0fe8949 100644
--- a/toolkit/components/pdfjs/content/PdfStreamConverter.sys.mjs
+++ b/toolkit/components/pdfjs/content/PdfStreamConverter.sys.mjs
@@ -1213,6 +1213,9 @@ PdfStreamConverter.prototype = {
       );
       // The viewer does not need to handle HTTP Refresh header.
       aRequest.setResponseHeader("Refresh", "", false);
+      // There is no reason to load something via <link>: the only external
+      // resource is the pdf itself.
+      aRequest.setResponseHeader("Link", "", false);
     }
 
     lazy.PdfJsTelemetryContent.onViewerIsUsed();
diff --git a/toolkit/components/pdfjs/test/browser.toml b/toolkit/components/pdfjs/test/browser.toml
index 50ec75f988b..e1f80550fa8 100644
--- a/toolkit/components/pdfjs/test/browser.toml
+++ b/toolkit/components/pdfjs/test/browser.toml
@@ -102,6 +102,9 @@ support-files = [
 
 ["browser_pdfjs_properties.js"]
 
+["browser_pdfjs_response_link.js"]
+support-files = ["pdf_response_link.sjs"]
+
 ["browser_pdfjs_saveas.js"]
 support-files = [
   "!/toolkit/content/tests/browser/common/mockTransfer.js",
diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_response_link.js b/toolkit/components/pdfjs/test/browser_pdfjs_response_link.js
new file mode 100644
index 00000000000..9b8833801d1
--- /dev/null
+++ b/toolkit/components/pdfjs/test/browser_pdfjs_response_link.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const RELATIVE_DIR = "toolkit/components/pdfjs/test/";
+const TESTROOT = "https://example.com/browser/" + RELATIVE_DIR;
+
+function getBodyBackgroundColor(browser) {
+  return SpecialPowers.spawn(browser, [], async () => {
+    return content.getComputedStyle(content.document.querySelector("body"))
+      .backgroundColor;
+  });
+}
+
+// Sanity check: the pdf test does not trivially pass due to the lack of support
+// for Link header.
+add_task(async function test_plain_text_with_link_in_response() {
+  await BrowserTestUtils.withNewTab(
+    { gBrowser, url: `${TESTROOT}pdf_response_link.sjs?text` },
+    async function (browser) {
+      const bodyBackgroundColor = await getBodyBackgroundColor(browser);
+      Assert.equal(
+        bodyBackgroundColor,
+        "rgb(255, 0, 0)",
+        "Body background is red"
+      );
+    }
+  );
+});
+
+add_task(async function test_pdf_with_link_in_response() {
+  makePDFJSHandler();
+
+  await BrowserTestUtils.withNewTab(
+    { gBrowser, url: "about:blank" },
+    async function (browser) {
+      await waitForPdfJSCanvas(browser, `${TESTROOT}pdf_response_link.sjs?pdf`);
+      const bodyBackgroundColor = await getBodyBackgroundColor(browser);
+      Assert.notEqual(
+        bodyBackgroundColor,
+        "rgb(255, 0, 0)",
+        "Body background is not red"
+      );
+      await waitForPdfJSClose(browser);
+    }
+  );
+});
diff --git a/toolkit/components/pdfjs/test/pdf_response_link.sjs b/toolkit/components/pdfjs/test/pdf_response_link.sjs
new file mode 100644
index 00000000000..fd5d3aff44f
--- /dev/null
+++ b/toolkit/components/pdfjs/test/pdf_response_link.sjs
@@ -0,0 +1,24 @@
+const DATA = {
+  pdf: {
+    mimetype: "application/pdf",
+    content:
+      "%PDF-1.\ntrailer<</Root<</Pages<</Kids[<</MediaBox[0 0 3 3]>>]>>>>>>",
+  },
+  text: {
+    mimetype: "text/plain",
+    content: "hello world",
+  },
+};
+
+function handleRequest(request, response) {
+  response.setHeader("Cache-Control", "no-cache", false);
+  response.setHeader(
+    "Link",
+    "<data:text/css,body{background:red%20!important;}>; rel=stylesheet",
+    false
+  );
+  response.setStatusLine(request.httpVersion, "200", "Found");
+  const { mimetype, content } = DATA[request.queryString];
+  response.setHeader("Content-Type", mimetype, false);
+  response.write(content);
+}
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/toolkit/components/pdfjs/test/browser.toml b/toolkit/components/pdfjs/test/browser.toml
index 50ec75f988b..e1f80550fa8 100644
--- a/toolkit/components/pdfjs/test/browser.toml
+++ b/toolkit/components/pdfjs/test/browser.toml
@@ -102,6 +102,9 @@ support-files = [
 
 ["browser_pdfjs_properties.js"]
 
+["browser_pdfjs_response_link.js"]
+support-files = ["pdf_response_link.sjs"]
+
 ["browser_pdfjs_saveas.js"]
 support-files = [
   "!/toolkit/content/tests/browser/common/mockTransfer.js",
diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_response_link.js b/toolkit/components/pdfjs/test/browser_pdfjs_response_link.js
new file mode 100644
index 00000000000..9b8833801d1
--- /dev/null
+++ b/toolkit/components/pdfjs/test/browser_pdfjs_response_link.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const RELATIVE_DIR = "toolkit/components/pdfjs/test/";
+const TESTROOT = "https://example.com/browser/" + RELATIVE_DIR;
+
+function getBodyBackgroundColor(browser) {
+  return SpecialPowers.spawn(browser, [], async () => {
+    return content.getComputedStyle(content.document.querySelector("body"))
+      .backgroundColor;
+  });
+}
+
+// Sanity check: the pdf test does not trivially pass due to the lack of support
+// for Link header.
+add_task(async function test_plain_text_with_link_in_response() {
+  await BrowserTestUtils.withNewTab(
+    { gBrowser, url: `${TESTROOT}pdf_response_link.sjs?text` },
+    async function (browser) {
+      const bodyBackgroundColor = await getBodyBackgroundColor(browser);
+      Assert.equal(
+        bodyBackgroundColor,
+        "rgb(255, 0, 0)",
+        "Body background is red"
+      );
+    }
+  );
+});
+
+add_task(async function test_pdf_with_link_in_response() {
+  makePDFJSHandler();
+
+  await BrowserTestUtils.withNewTab(
+    { gBrowser, url: "about:blank" },
+    async function (browser) {
+      await waitForPdfJSCanvas(browser, `${TESTROOT}pdf_response_link.sjs?pdf`);
+      const bodyBackgroundColor = await getBodyBackgroundColor(browser);
+      Assert.notEqual(
+        bodyBackgroundColor,
+        "rgb(255, 0, 0)",
+        "Body background is not red"
+      );
+      await waitForPdfJSClose(browser);
+    }
+  );
+});
diff --git a/toolkit/components/pdfjs/test/pdf_response_link.sjs b/toolkit/components/pdfjs/test/pdf_response_link.sjs
new file mode 100644
index 00000000000..fd5d3aff44f
--- /dev/null
+++ b/toolkit/components/pdfjs/test/pdf_response_link.sjs
@@ -0,0 +1,24 @@
+const DATA = {
+  pdf: {
+    mimetype: "application/pdf",
+    content:
+      "%PDF-1.\ntrailer<</Root<</Pages<</Kids[<</MediaBox[0 0 3 3]>>]>>>>>>",
+  },
+  text: {
+    mimetype: "text/plain",
+    content: "hello world",
+  },
+};
+
+function handleRequest(request, response) {
+  response.setHeader("Cache-Control", "no-cache", false);
+  response.setHeader(
+    "Link",
+    "<data:text/css,body{background:red%20!important;}>; rel=stylesheet",
+    false
+  );
+  response.setStatusLine(request.httpVersion, "200", "Found");
+  const { mimetype, content } = DATA[request.queryString];
+  response.setHeader("Content-Type", mimetype, false);
+  response.write(content);
+}
Loading diff…