Medium firefox Logic Error 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionMitigation bypass in the Networking: HTTP component
ComponentDOM
Bug ClassLogic Error
Tracker2003766
Fix commit7b2a8b4bb6f2 (firefox) +100/-65
CISA KEVNot listed
Creditedpizzahunthack1
Disclosed2026-03-24

Changed Functions

FunctionChangeNotes
if
dom/base/MimeType.cpp
modified
ResponseHeaderVisitor
netwerk/protocol/http/nsHttpResponseHead.cpp
modified
MOZ_REQUIRES
netwerk/protocol/http/nsHttpResponseHead.cpp
modified

Files Changed

  • dom/base/MimeType.cpp
  • dom/base/test/gtest/TestMimeType.cpp
  • modules/libpref/init/StaticPrefList.yaml
  • netwerk/protocol/http/nsHttpResponseHead.cpp
  • netwerk/protocol/http/nsHttpResponseHead.h
  • testing/web-platform/meta/fetch/content-type/response.window.js.ini
  • testing/web-platform/meta/fetch/content-type/script.window.js.ini
diff --git a/dom/base/MimeType.cpp b/dom/base/MimeType.cpp
index 48c3ef91921..d5afad73970 100644
--- a/dom/base/MimeType.cpp
+++ b/dom/base/MimeType.cpp
@@ -272,6 +272,7 @@ template <typename char_type>
     const nsTSubstring<char_type>& aMimeType,
     nsTSubstring<char_type>& aOutEssence,
     nsTSubstring<char_type>& aOutCharset) {
+  // https://fetch.spec.whatwg.org/#concept-header-extract-mime-type
   static char_type kCHARSET[] = {'c', 'h', 'a', 'r', 's', 'e', 't'};
   static nsTDependentSubstring<char_type> kCharset(kCHARSET, 7);
 
@@ -279,8 +280,8 @@ template <typename char_type>
   nsTAutoString<char_type> prevContentType;
   nsTAutoString<char_type> prevCharset;
 
-  prevContentType.Assign(aOutEssence);
-  prevCharset.Assign(aOutCharset);
+  aOutEssence.Truncate();
+  aOutCharset.Truncate();
 
   nsTArray<nsTDependentSubstring<char_type>> mimeTypeParts =
       SplitMimetype(aMimeType);
@@ -293,9 +294,7 @@ template <typename char_type>
     parsed = Parse(mimeTypeString);
 
     if (!parsed) {
-      aOutEssence.Truncate();
-      aOutCharset.Truncate();
-      return false;
+      continue;
     }
 
     parsed->GetEssence(aOutEssence);
@@ -323,6 +322,10 @@ template <typename char_type>
     }
   }
 
+  if (aOutEssence.IsEmpty()) {
+    return false;
+  }
+
   return true;
 }
 
diff --git a/dom/base/test/gtest/TestMimeType.cpp b/dom/base/test/gtest/TestMimeType.cpp
index 82eec24e495..3ae2ca9f7cb 100644
--- a/dom/base/test/gtest/TestMimeType.cpp
+++ b/dom/base/test/gtest/TestMimeType.cpp
@@ -820,8 +820,8 @@ TEST(MimeTypeParsing, contentTypes1)
 
   bool parsed = CMimeType::Parse(val, contentType, contentCharset);
 
-  ASSERT_FALSE(parsed);
-  ASSERT_TRUE(contentType.EqualsLiteral(""));
+  ASSERT_TRUE(parsed);
+  ASSERT_TRUE(contentType.EqualsLiteral("text/plain"));
   ASSERT_TRUE(contentCharset.EqualsLiteral(""));
 }
 
@@ -1133,3 +1133,27 @@ TEST(MimeTypeParsing, countParameters3)
   ASSERT_TRUE(parsed);
   ASSERT_TRUE(parsed->GetParameterCount() == 3);
 }
+
+TEST(MimeTypeParsing, EmptyParsing)
+{
+  constexpr nsLiteralCString val("");
+  nsCString contentType;
+  nsCString contentCharset;
+  bool parsed = CMimeType::Parse(val, contentType, contentCharset);
+
+  ASSERT_FALSE(parsed);
+  ASSERT_TRUE(contentType.EqualsLiteral(""));
+  ASSERT_TRUE(contentCharset.EqualsLiteral(""));
+}
+
+TEST(MimeTypeParsing, EmptySubtype)
+{
+  constexpr nsLiteralCString val("audio/");
+  nsCString contentType;
+  nsCString contentCharset;
+  bool parsed = CMimeType::Parse(val, contentType, contentCharset);
+
+  ASSERT_FALSE(parsed);
+  ASSERT_TRUE(contentType.EqualsLiteral(""));
+  ASSERT_TRUE(contentCharset.EqualsLiteral(""));
+}
diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml
index 3fdd1a4da1a..8c2298fbb67 100644
--- a/modules/libpref/init/StaticPrefList.yaml
+++ b/modules/libpref/init/StaticPrefList.yaml
@@ -14008,6 +14008,12 @@
     login-us.microsoftonline.com
   mirror: never
 
+# Whether to fallback to old mime-type parser when CMimeType::Parse fails
+- name: network.http.fallback_to_net_parse_ct
+  type: RelaxedAtomicBool
+  value: true
+  mirror: always
+
 # The factor by which to increase the keepalive timeout when the
 # NS_HTTP_LARGE_KEEPALIVE flag is used for a connection
 - name: network.http.largeKeepaliveFactor
diff --git a/netwerk/protocol/http/nsHttpResponseHead.cpp b/netwerk/protocol/http/nsHttpResponseHead.cpp
index d183937a22e..3c016359844 100644
--- a/netwerk/protocol/http/nsHttpResponseHead.cpp
+++ b/netwerk/protocol/http/nsHttpResponseHead.cpp
@@ -305,6 +305,25 @@ void nsHttpResponseHead::FlattenNetworkOriginalHeaders(nsACString& buf) {
   mHeaders.FlattenOriginalHeader(buf);
 }
 
+class ResponseHeaderVisitor : public nsIHttpHeaderVisitor {
+  using callbackType =
+      std::function<void(const nsACString& aName, const nsACString& aValue)>;
+  NS_DECL_ISUPPORTS
+  explicit ResponseHeaderVisitor(callbackType&& aCallback)
+      : mCallback(std::move(aCallback)) {}
+
+  NS_IMETHOD VisitHeader(const nsACString& aName,
+                         const nsACString& aValue) override {
+    mCallback(aName, aValue);
+    return NS_OK;
+  }
+
+ private:
+  virtual ~ResponseHeaderVisitor() = default;
+  callbackType mCallback;
+};
+NS_IMPL_ISUPPORTS(ResponseHeaderVisitor, nsIHttpHeaderVisitor)
+
 nsresult nsHttpResponseHead::ParseCachedHead(const char* block) {
   RecursiveMutexAutoLock monitor(mRecursiveMutex);
   LOG(("nsHttpResponseHead::ParseCachedHead [this=%p]\n", this));
@@ -330,6 +349,15 @@ nsresult nsHttpResponseHead::ParseCachedHead(const char* block) {
 
   } while (true);
 
+  // fixup content-type header.
+  mContentTypeBuffer.Truncate();
+  RefPtr<ResponseHeaderVisitor> visitor = new ResponseHeaderVisitor(
+      [&](const nsACString& aName, const nsACString& aValue)
+          MOZ_REQUIRES(mRecursiveMutex) {
+            MOZ_ASSERT(nsHttp::Content_Type.val().EqualsIgnoreCase(aName));
+            ParseContentTypeValue(nsHttp::ResolveAtom(aName), aValue);
+          });
+  (void)mHeaders.GetOriginalHeader(nsHttp::Content_Type, visitor);
   return NS_OK;
 }
 
@@ -456,6 +484,33 @@ nsresult nsHttpResponseHead::ParseHeaderLine(const nsACString& line) {
   return ParseHeaderLine_locked(line, true);
 }
 
+void nsHttpResponseHead::ParseContentTypeValue(const nsHttpAtom& aAtom,
+                                               const nsACString& aValue) {
+  if (!mContentTypeBuffer.IsEmpty()) {
+    mContentTypeBuffer.AppendLiteral(",");
+  }
+  mContentTypeBuffer.Append(aValue);
+  mContentType.Truncate();
+  mContentCharset.Truncate();
+  if (CMimeType::Parse(mContentTypeBuffer, mContentType, mContentCharset)) {
+  } else if (StaticPrefs::network_http_fallback_to_net_parse_ct()) {
+    bool dummy;
+    net_ParseContentType(aValue, mContentType, mContentCharset, &dummy);
+  }
+  LOG(("ParseContentType [input=%s, type=%s, charset=%s]\n",
+       nsPromiseFlatCString(aValue).get(), mContentType.get(),
+       mContentCharset.get()));
+
+  nsAutoCString existingHeader;
+  if (NS_SUCCEEDED(mHeaders.GetHeader(aAtom, existingHeader)) &&
+      existingHeader != mContentTypeBuffer) {
+    // Always set the header to the merged buffer, as per Fetch spec.
+    DebugOnly<nsresult> rv = mHeaders.SetHeader(
+        aAtom, mContentTypeBuffer, false, nsHttpHeaderArray::eVarietyResponse);
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+  }
+}
+
 nsresult nsHttpResponseHead::ParseHeaderLine_locked(
     const nsACString& line, bool originalFromNetHeaders) {
   nsHttpAtom hdr;
@@ -499,13 +554,7 @@ nsresult nsHttpResponseHead::ParseHeaderLine_locked(
     }
 
   } else if (hdr == nsHttp::Content_Type) {
-    if (CMimeType::Parse(val, mContentType, mContentCharset)) {
-    } else {
-      bool dummy;
-      net_ParseContentType(val, mContentType, mContentCharset, &dummy);
-    }
-    LOG(("ParseContentType [input=%s, type=%s, charset=%s]\n", val.get(),
-         mContentType.get(), mContentCharset.get()));
+    ParseContentTypeValue(hdr, val);
   } else if (hdr == nsHttp::Cache_Control) {
     ParseCacheControl(mHeaders.PeekHeader(hdr));
   } else if (hdr == nsHttp::Pragma) {
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/dom/base/test/gtest/TestMimeType.cpp b/dom/base/test/gtest/TestMimeType.cpp
index 82eec24e495..3ae2ca9f7cb 100644
--- a/dom/base/test/gtest/TestMimeType.cpp
+++ b/dom/base/test/gtest/TestMimeType.cpp
@@ -820,8 +820,8 @@ TEST(MimeTypeParsing, contentTypes1)
 
   bool parsed = CMimeType::Parse(val, contentType, contentCharset);
 
-  ASSERT_FALSE(parsed);
-  ASSERT_TRUE(contentType.EqualsLiteral(""));
+  ASSERT_TRUE(parsed);
+  ASSERT_TRUE(contentType.EqualsLiteral("text/plain"));
   ASSERT_TRUE(contentCharset.EqualsLiteral(""));
 }
 
@@ -1133,3 +1133,27 @@ TEST(MimeTypeParsing, countParameters3)
   ASSERT_TRUE(parsed);
   ASSERT_TRUE(parsed->GetParameterCount() == 3);
 }
+
+TEST(MimeTypeParsing, EmptyParsing)
+{
+  constexpr nsLiteralCString val("");
+  nsCString contentType;
+  nsCString contentCharset;
+  bool parsed = CMimeType::Parse(val, contentType, contentCharset);
+
+  ASSERT_FALSE(parsed);
+  ASSERT_TRUE(contentType.EqualsLiteral(""));
+  ASSERT_TRUE(contentCharset.EqualsLiteral(""));
+}
+
+TEST(MimeTypeParsing, EmptySubtype)
+{
+  constexpr nsLiteralCString val("audio/");
+  nsCString contentType;
+  nsCString contentCharset;
+  bool parsed = CMimeType::Parse(val, contentType, contentCharset);
+
+  ASSERT_FALSE(parsed);
+  ASSERT_TRUE(contentType.EqualsLiteral(""));
+  ASSERT_TRUE(contentCharset.EqualsLiteral(""));
+}
diff --git a/testing/web-platform/meta/fetch/content-type/response.window.js.ini b/testing/web-platform/meta/fetch/content-type/response.window.js.ini
index 72c4c987257..3f36a2f6bc7 100644
--- a/testing/web-platform/meta/fetch/content-type/response.window.js.ini
+++ b/testing/web-platform/meta/fetch/content-type/response.window.js.ini
@@ -1,43 +1,10 @@
 [response.window.html]
-  [fetch(): separate response Content-Type: */* text/html]
-    expected: FAIL
-
   [fetch(): separate response Content-Type: text/html;x=" text/plain]
     expected: FAIL
 
   [fetch(): combined response Content-Type: text/html;x=" text/plain]
     expected: FAIL
 
-  [<iframe>: separate response Content-Type: text/html;" text/plain]
-    expected: FAIL
-
-  [fetch(): separate response Content-Type: text/html;charset=gbk text/plain text/html]
-    expected: FAIL
-
-  [fetch(): separate response Content-Type: text/plain;charset=gbk text/plain;charset=windows-1252]
-    expected: FAIL
-
-  [<iframe>: separate response Content-Type: text/html;x=" text/plain]
-    expected: FAIL
-
-  [fetch(): separate response Content-Type: text/html;charset=gbk text/html;x=",text/plain]
-    expected: FAIL
-
-  [fetch(): separate response Content-Type: text/plain;charset=gbk text/html]
-    expected: FAIL
-
-  [fetch(): separate response Content-Type: text/plain;charset=gbk text/html;charset=windows-1254]
-    expected: FAIL
-
-  [fetch(): separate response Content-Type: text/html text/plain]
-    expected: FAIL
-
-  [fetch(): separate response Content-Type:  text/plain]
-    expected: FAIL
-
-  [<iframe>: separate response Content-Type: text/html;" \\" text/plain]
-    expected: FAIL
-
   [Request: combined response Content-Type: text/html;" \\" text/plain ";charset=GBK]
     expected: FAIL
 
@@ -47,15 +14,6 @@
   [fetch(): separate response Content-Type: text/html;" \\" text/plain ";charset=GBK]
     expected: FAIL
 
-  [fetch(): separate response Content-Type: text/plain;charset=gbk;x=foo text/plain]
-    expected: FAIL
-
-  [<iframe>: separate response Content-Type: text/html;" \\" text/plain ";charset=GBK]
-    expected: FAIL
-
-  [fetch(): separate response Content-Type: text/html;" " text/plain]
-    expected: FAIL
-
   [Response: combined response Content-Type: text/html;" \\" text/plain ";charset=GBK]
     expected: FAIL
diff --git a/testing/web-platform/meta/fetch/content-type/script.window.js.ini b/testing/web-platform/meta/fetch/content-type/script.window.js.ini
index c1d9d4926c9..a3db2c9dcef 100644
--- a/testing/web-platform/meta/fetch/content-type/script.window.js.ini
+++ b/testing/web-platform/meta/fetch/content-type/script.window.js.ini
@@ -1,13 +1,4 @@
 [script.window.html]
-  [separate text/javascript;" x/x]
-    expected: FAIL
-
-  [separate text/javascript;charset=windows-1252;" \\" x/x]
-    expected: FAIL
-
-  [separate x/x;" x/y;\\" text/javascript;charset=windows-1252;" text/javascript]
-    expected: FAIL
-
   [combined text/javascript error]
     expected:
       if os == "win": [PASS, FAIL]
Loading diff…