Low firefox Logic Error 🔧 Commit mapped

Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactlow
DescriptionInformation disclosure in the Networking: Cache component
ComponentNetworking
Bug ClassLogic Error
Tracker1981502
Fix commita664230b4e2d (firefox) +111/-1
CISA KEVNot listed
CreditedIbuki Sato
Disclosed2025-09-16

Changed Functions

FunctionChangeNotes
if
netwerk/test/unit/test_cache2_nostore.js
modified
add_task
netwerk/test/unit/test_cache2_nostore.js
modified
for
netwerk/test/unit/test_cache2_nostore.js
modified

Files Changed

  • netwerk/protocol/http/nsHttpResponseHead.cpp
  • netwerk/test/gtest/TestCacheControlParser.cpp
  • netwerk/test/gtest/TestHttpResponseHead.cpp
  • netwerk/test/unit/test_cache2_nostore.js
  • netwerk/test/unit/xpcshell.toml
diff --git a/netwerk/protocol/http/nsHttpResponseHead.cpp b/netwerk/protocol/http/nsHttpResponseHead.cpp
index aa0e1662996..f457ac7e23a 100644
--- a/netwerk/protocol/http/nsHttpResponseHead.cpp
+++ b/netwerk/protocol/http/nsHttpResponseHead.cpp
@@ -496,7 +496,7 @@ nsresult nsHttpResponseHead::ParseHeaderLine_locked(
     LOG(("ParseContentType [input=%s, type=%s, charset=%s]\n", val.get(),
          mContentType.get(), mContentCharset.get()));
   } else if (hdr == nsHttp::Cache_Control) {
-    ParseCacheControl(val.get());
+    ParseCacheControl(mHeaders.PeekHeader(hdr));
   } else if (hdr == nsHttp::Pragma) {
     ParsePragma(val.get());
   }
diff --git a/netwerk/test/gtest/TestCacheControlParser.cpp b/netwerk/test/gtest/TestCacheControlParser.cpp
index 40ce3d153a9..8b6613a9a7c 100644
--- a/netwerk/test/gtest/TestCacheControlParser.cpp
+++ b/netwerk/test/gtest/TestCacheControlParser.cpp
@@ -98,3 +98,10 @@ TEST(TestCacheControlParser, CaseInsensitive)
   ASSERT_TRUE(cc2.StaleWhileRevalidate(&stale_while_revalidate));
   ASSERT_EQ(stale_while_revalidate, 8U);
 }
+
+TEST(TestCacheControlParser, NoCacheAfterNoStore)
+{
+  CacheControlParser cc("no-store, no-cache"_ns);
+  ASSERT_TRUE(cc.NoCache());
+  ASSERT_TRUE(cc.NoStore());
+}
diff --git a/netwerk/test/gtest/TestHttpResponseHead.cpp b/netwerk/test/gtest/TestHttpResponseHead.cpp
index c38f24c1eab..b7df9a66ade 100644
--- a/netwerk/test/gtest/TestHttpResponseHead.cpp
+++ b/netwerk/test/gtest/TestHttpResponseHead.cpp
@@ -199,5 +199,18 @@ TEST(TestHttpResponseHead, MoveConstructor)
   ASSERT_FALSE(originalHead.HasHeader(nsHttp::Content_Length));
 }
 
+TEST(TestHttpResponseHead, MultipleCacheControl)
+{
+  nsHttpResponseHead originalHead;
+  Unused << originalHead.ParseStatusLine("HTTP/1.1 200 OK"_ns);
+  Unused << originalHead.ParseHeaderLine("content-type: text/plain"_ns);
+  Unused << originalHead.ParseHeaderLine("content-length: 1408"_ns);
+  Unused << originalHead.ParseHeaderLine("cache-control: no-cache"_ns);
+  Unused << originalHead.ParseHeaderLine("cache-control: no-store"_ns);
+
+  ASSERT_EQ(originalHead.NoStore(), true);
+  ASSERT_EQ(originalHead.NoCache(), true);
+}
+
 }  // namespace net
 }  // namespace mozilla
diff --git a/netwerk/test/unit/test_cache2_nostore.js b/netwerk/test/unit/test_cache2_nostore.js
new file mode 100644
index 00000000000..77b41ed147a
--- /dev/null
+++ b/netwerk/test/unit/test_cache2_nostore.js
@@ -0,0 +1,88 @@
+/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 sw=2 sts=2 et: */
+
+/**
+ * @fileoverview
+ *   Unit test to verify that resources with Cache-Control: no-store, no-cache
+ *   and Cache-Control: no-cache, no-store headers are not saved in the cache.
+ */
+
+const { HttpServer } = ChromeUtils.importESModule(
+  "resource://testing-common/httpd.sys.mjs"
+);
+
+let httpServer = null;
+
+function startServer() {
+  httpServer = new HttpServer();
+  httpServer.registerPathHandler("/test1", (request, response) => {
+    response.setHeader("Cache-Control", "no-cache, no-store", false);
+    response.setHeader("Content-Type", "text/plain", false);
+    response.write("OK");
+  });
+  httpServer.registerPathHandler("/test2", (request, response) => {
+    response.setHeader("Cache-Control", "no-cache, no-store", false);
+    response.setHeader("Content-Type", "text/plain", false);
+    response.write("OK");
+  });
+  httpServer.registerPathHandler("/test3", (request, response) => {
+    response.setHeader("Cache-Control", "no-cache", false);
+    response.setHeader("Cache-Control", "no-store", false);
+    response.setHeader("Content-Type", "text/plain", false);
+    response.write("OK");
+  });
+  httpServer.registerPathHandler("/test4", (request, response) => {
+    response.setHeader("Cache-Control", "no-cache", false);
+    response.setHeader("Cache-Control", "no-store", false);
+    response.setHeader("Content-Type", "text/plain", false);
+    response.write("OK");
+  });
+  httpServer.start(-1);
+  registerCleanupFunction(async () => {
+    if (httpServer) {
+      await httpServer.stop();
+    }
+  });
+  return httpServer.identity.primaryPort;
+}
+
+add_task(async function test_no_cache_no_store() {
+  const port = startServer();
+  const baseURI = `http://localhost:${port}`;
+  let tests = ["/test1", "/test2", "/test3", "/test4"];
+
+  for (let test of tests) {
+    let uri = baseURI + test;
+    let channel = NetUtil.newChannel({
+      uri,
+      loadUsingSystemPrincipal: true,
+    });
+
+    let buffer = await new Promise(resolve => {
+      channel.asyncOpen(
+        new ChannelListener(
+          (request, buffer) => resolve(buffer),
+          null,
+          CL_ALLOW_UNKNOWN_CL
+        )
+      );
+    });
+
+    Assert.equal(buffer, "OK", `Received expected content for ${test}`);
+
+    let entry = await new Promise(resolve => {
+      asyncOpenCacheEntry(
+        uri,
+        "disk",
+        Ci.nsICacheStorage.OPEN_READONLY,
+        null,
+        (status, entry) => resolve(entry)
+      );
+    });
+    Assert.equal(
+      entry.persistent,
+      false,
+      `${test} should not be persistently cached`
+    );
+  }
+});
diff --git a/netwerk/test/unit/xpcshell.toml b/netwerk/test/unit/xpcshell.toml
index a43502024ca..1b73868f64c 100644
--- a/netwerk/test/unit/xpcshell.toml
+++ b/netwerk/test/unit/xpcshell.toml
@@ -467,6 +467,8 @@ run-sequentially = "httpd server"
 ["test_cache2_clear_with_usercontext_oa.js"]
 run-sequentially = "httpd server"
 
+["test_cache2_nostore.js"]
+
 ["test_cache_204_response.js"]
 
 ["test_cache_jar.js"]
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/netwerk/test/gtest/TestCacheControlParser.cpp b/netwerk/test/gtest/TestCacheControlParser.cpp
index 40ce3d153a9..8b6613a9a7c 100644
--- a/netwerk/test/gtest/TestCacheControlParser.cpp
+++ b/netwerk/test/gtest/TestCacheControlParser.cpp
@@ -98,3 +98,10 @@ TEST(TestCacheControlParser, CaseInsensitive)
   ASSERT_TRUE(cc2.StaleWhileRevalidate(&stale_while_revalidate));
   ASSERT_EQ(stale_while_revalidate, 8U);
 }
+
+TEST(TestCacheControlParser, NoCacheAfterNoStore)
+{
+  CacheControlParser cc("no-store, no-cache"_ns);
+  ASSERT_TRUE(cc.NoCache());
+  ASSERT_TRUE(cc.NoStore());
+}
diff --git a/netwerk/test/gtest/TestHttpResponseHead.cpp b/netwerk/test/gtest/TestHttpResponseHead.cpp
index c38f24c1eab..b7df9a66ade 100644
--- a/netwerk/test/gtest/TestHttpResponseHead.cpp
+++ b/netwerk/test/gtest/TestHttpResponseHead.cpp
@@ -199,5 +199,18 @@ TEST(TestHttpResponseHead, MoveConstructor)
   ASSERT_FALSE(originalHead.HasHeader(nsHttp::Content_Length));
 }
 
+TEST(TestHttpResponseHead, MultipleCacheControl)
+{
+  nsHttpResponseHead originalHead;
+  Unused << originalHead.ParseStatusLine("HTTP/1.1 200 OK"_ns);
+  Unused << originalHead.ParseHeaderLine("content-type: text/plain"_ns);
+  Unused << originalHead.ParseHeaderLine("content-length: 1408"_ns);
+  Unused << originalHead.ParseHeaderLine("cache-control: no-cache"_ns);
+  Unused << originalHead.ParseHeaderLine("cache-control: no-store"_ns);
+
+  ASSERT_EQ(originalHead.NoStore(), true);
+  ASSERT_EQ(originalHead.NoCache(), true);
+}
+
 }  // namespace net
 }  // namespace mozilla
diff --git a/netwerk/test/unit/test_cache2_nostore.js b/netwerk/test/unit/test_cache2_nostore.js
new file mode 100644
index 00000000000..77b41ed147a
--- /dev/null
+++ b/netwerk/test/unit/test_cache2_nostore.js
@@ -0,0 +1,88 @@
+/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 sw=2 sts=2 et: */
+
+/**
+ * @fileoverview
+ *   Unit test to verify that resources with Cache-Control: no-store, no-cache
+ *   and Cache-Control: no-cache, no-store headers are not saved in the cache.
+ */
+
+const { HttpServer } = ChromeUtils.importESModule(
+  "resource://testing-common/httpd.sys.mjs"
+);
+
+let httpServer = null;
+
+function startServer() {
+  httpServer = new HttpServer();
+  httpServer.registerPathHandler("/test1", (request, response) => {
+    response.setHeader("Cache-Control", "no-cache, no-store", false);
+    response.setHeader("Content-Type", "text/plain", false);
+    response.write("OK");
+  });
+  httpServer.registerPathHandler("/test2", (request, response) => {
+    response.setHeader("Cache-Control", "no-cache, no-store", false);
+    response.setHeader("Content-Type", "text/plain", false);
+    response.write("OK");
+  });
+  httpServer.registerPathHandler("/test3", (request, response) => {
+    response.setHeader("Cache-Control", "no-cache", false);
+    response.setHeader("Cache-Control", "no-store", false);
+    response.setHeader("Content-Type", "text/plain", false);
+    response.write("OK");
+  });
+  httpServer.registerPathHandler("/test4", (request, response) => {
+    response.setHeader("Cache-Control", "no-cache", false);
+    response.setHeader("Cache-Control", "no-store", false);
+    response.setHeader("Content-Type", "text/plain", false);
+    response.write("OK");
+  });
+  httpServer.start(-1);
+  registerCleanupFunction(async () => {
+    if (httpServer) {
+      await httpServer.stop();
+    }
+  });
+  return httpServer.identity.primaryPort;
+}
+
+add_task(async function test_no_cache_no_store() {
+  const port = startServer();
+  const baseURI = `http://localhost:${port}`;
+  let tests = ["/test1", "/test2", "/test3", "/test4"];
+
+  for (let test of tests) {
+    let uri = baseURI + test;
+    let channel = NetUtil.newChannel({
+      uri,
+      loadUsingSystemPrincipal: true,
+    });
+
+    let buffer = await new Promise(resolve => {
+      channel.asyncOpen(
+        new ChannelListener(
+          (request, buffer) => resolve(buffer),
+          null,
+          CL_ALLOW_UNKNOWN_CL
+        )
+      );
+    });
+
+    Assert.equal(buffer, "OK", `Received expected content for ${test}`);
+
+    let entry = await new Promise(resolve => {
+      asyncOpenCacheEntry(
+        uri,
+        "disk",
+        Ci.nsICacheStorage.OPEN_READONLY,
+        null,
+        (status, entry) => resolve(entry)
+      );
+    });
+    Assert.equal(
+      entry.persistent,
+      false,
+      `${test} should not be persistently cached`
+    );
+  }
+});
diff --git a/netwerk/test/unit/xpcshell.toml b/netwerk/test/unit/xpcshell.toml
index a43502024ca..1b73868f64c 100644
--- a/netwerk/test/unit/xpcshell.toml
+++ b/netwerk/test/unit/xpcshell.toml
@@ -467,6 +467,8 @@ run-sequentially = "httpd server"
 ["test_cache2_clear_with_usercontext_oa.js"]
 run-sequentially = "httpd server"
 
+["test_cache2_nostore.js"]
+
 ["test_cache_204_response.js"]
 
 ["test_cache_jar.js"]
Loading diff…