Firefox · DOM
CVE-2026-4700
Logic Error in DOM
Overview
Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifdom/base/MimeType.cpp |
modified | |
ResponseHeaderVisitornetwerk/protocol/http/nsHttpResponseHead.cpp |
modified | |
MOZ_REQUIRESnetwerk/protocol/http/nsHttpResponseHead.cpp |
modified |
Files Changed
dom/base/MimeType.cppdom/base/test/gtest/TestMimeType.cppmodules/libpref/init/StaticPrefList.yamlnetwerk/protocol/http/nsHttpResponseHead.cppnetwerk/protocol/http/nsHttpResponseHead.htesting/web-platform/meta/fetch/content-type/response.window.js.initesting/web-platform/meta/fetch/content-type/script.window.js.ini
Patch
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…
References
On This Page