diff --git a/expat-CVE-2023-52425.patch b/expat-CVE-2023-52425.patch new file mode 100644 index 0000000..bdd06ca --- /dev/null +++ b/expat-CVE-2023-52425.patch @@ -0,0 +1,375 @@ +# erAck: backport of expat CVE-2023-52425 DoS fix +# https://github.com/libexpat/libexpat/commit/34b598c5f594b015c513c73f06e7ced3323edbf1 +# +--- firefox-115.9.0/parser/expat/lib/expat.h.expat-CVE-2023-52425 2024-03-11 20:36:11.000000000 +0100 ++++ firefox-115.9.0/parser/expat/lib/expat.h 2024-03-13 20:46:45.648505015 +0100 +@@ -1045,6 +1045,10 @@ XMLPARSEAPI(const XML_Feature *) + XML_GetFeatureList(void); + + ++/* Added in Expat 2.6.0. */ ++XMLPARSEAPI(XML_Bool) ++XML_SetReparseDeferralEnabled(XML_Parser parser, XML_Bool enabled); ++ + /* Expat follows the semantic versioning convention. + See http://semver.org. + */ +--- firefox-115.9.0/parser/expat/lib/internal.h.expat-CVE-2023-52425 2024-03-11 20:36:11.000000000 +0100 ++++ firefox-115.9.0/parser/expat/lib/internal.h 2024-03-14 00:14:39.334319725 +0100 +@@ -80,6 +80,7 @@ + # endif + #endif + ++#include "expat.h" // so we can use type XML_Parser below + + #ifdef __cplusplus + extern "C" { +@@ -90,6 +91,9 @@ void + align_limit_to_full_utf8_characters(const char * from, const char ** fromLimRef); + + ++extern XML_Bool g_reparseDeferralEnabledDefault; // written ONLY in runtests.c ++extern unsigned int g_parseAttempts; // used for testing only ++ + #ifdef __cplusplus + } + #endif +--- firefox-115.9.0/parser/expat/lib/xmlparse.c.expat-CVE-2023-52425 2024-03-11 20:36:11.000000000 +0100 ++++ firefox-115.9.0/parser/expat/lib/xmlparse.c 2024-03-13 22:55:14.844756009 +0100 +@@ -6,6 +6,7 @@ + + #define _GNU_SOURCE /* syscall prototype */ + ++#include + #include + #include /* memset(), memcpy() */ + #include +@@ -89,6 +90,9 @@ typedef char ICHAR; + /* Round up n to be a multiple of sz, where sz is a power of 2. */ + #define ROUND_UP(n, sz) (((n) + ((sz) - 1)) & ~((sz) - 1)) + ++/* Do safe (NULL-aware) pointer arithmetic */ ++#define EXPAT_SAFE_PTR_DIFF(p, q) (((p) && (q)) ? ((p) - (q)) : 0) ++ + /* Handle the case where memmove() doesn't exist. */ + #ifndef HAVE_MEMMOVE + #ifdef HAVE_BCOPY +@@ -98,6 +102,8 @@ typedef char ICHAR; + #endif /* HAVE_BCOPY */ + #endif /* HAVE_MEMMOVE */ + ++#define EXPAT_MIN(a, b) (((a) < (b)) ? (a) : (b)) ++ + #include "internal.h" + #include "xmltok.h" + #include "xmlrole.h" +@@ -476,6 +482,9 @@ parserInit(XML_Parser parser, const XML_ + ? 0 \ + : ((*((pool)->ptr)++ = c), 1)) + ++XML_Bool g_reparseDeferralEnabledDefault = XML_TRUE; // write ONLY in runtests.c ++unsigned int g_parseAttempts = 0; // used for testing only ++ + struct XML_ParserStruct { + /* The first member must be userData so that the XML_GetUserData + macro works. */ +@@ -491,6 +500,9 @@ struct XML_ParserStruct { + const char *m_bufferLim; + XML_Index m_parseEndByteIndex; + const char *m_parseEndPtr; ++ size_t m_partialTokenBytesBefore; /* used in heuristic to avoid O(n^2) */ ++ XML_Bool m_reparseDeferralEnabled; ++ int m_lastBufferRequestSize; + XML_Char *m_dataBuf; + XML_Char *m_dataBufEnd; + XML_StartElementHandler m_startElementHandler; +@@ -647,6 +659,9 @@ struct XML_ParserStruct { + #define bufferEnd (parser->m_bufferEnd) + #define parseEndByteIndex (parser->m_parseEndByteIndex) + #define parseEndPtr (parser->m_parseEndPtr) ++#define partialTokenBytesBefore (parser->m_partialTokenBytesBefore) ++#define reparseDeferralEnabled (parser->m_reparseDeferralEnabled) ++#define lastBufferRequestSize (parser->m_lastBufferRequestSize) + #define bufferLim (parser->m_bufferLim) + #define dataBuf (parser->m_dataBuf) + #define dataBufEnd (parser->m_dataBufEnd) +@@ -887,6 +902,47 @@ get_hash_secret_salt(XML_Parser parser) + return parser->m_hash_secret_salt; + } + ++static enum XML_Error ++callProcessor(XML_Parser parser, const char *start, const char *end, ++ const char **endPtr) { ++ const size_t have_now = EXPAT_SAFE_PTR_DIFF(end, start); ++ ++ if (parser->m_reparseDeferralEnabled ++ && ! parser->m_parsingStatus.finalBuffer) { ++ // Heuristic: don't try to parse a partial token again until the amount of ++ // available data has increased significantly. ++ const size_t had_before = parser->m_partialTokenBytesBefore; ++ // ...but *do* try anyway if we're close to causing a reallocation. ++ size_t available_buffer ++ = EXPAT_SAFE_PTR_DIFF(parser->m_bufferPtr, parser->m_buffer); ++#if XML_CONTEXT_BYTES > 0 ++ available_buffer -= EXPAT_MIN(available_buffer, XML_CONTEXT_BYTES); ++#endif ++ available_buffer ++ += EXPAT_SAFE_PTR_DIFF(parser->m_bufferLim, parser->m_bufferEnd); ++ // m_lastBufferRequestSize is never assigned a value < 0, so the cast is ok ++ const bool enough ++ = (have_now >= 2 * had_before) ++ || ((size_t)parser->m_lastBufferRequestSize > available_buffer); ++ ++ if (! enough) { ++ *endPtr = start; // callers may expect this to be set ++ return XML_ERROR_NONE; ++ } ++ } ++ g_parseAttempts += 1; ++ const enum XML_Error ret = parser->m_processor(parser, start, end, endPtr); ++ if (ret == XML_ERROR_NONE) { ++ // if we consumed nothing, remember what we had on this parse attempt. ++ if (*endPtr == start) { ++ parser->m_partialTokenBytesBefore = have_now; ++ } else { ++ parser->m_partialTokenBytesBefore = 0; ++ } ++ } ++ return ret; ++} ++ + static XML_Bool /* only valid for root parser */ + startParsing(XML_Parser parser) + { +@@ -1075,6 +1131,9 @@ parserInit(XML_Parser parser, const XML_ + bufferEnd = buffer; + parseEndByteIndex = 0; + parseEndPtr = NULL; ++ partialTokenBytesBefore = 0; ++ reparseDeferralEnabled = g_reparseDeferralEnabledDefault; ++ lastBufferRequestSize = 0; + declElementType = NULL; + declAttributeId = NULL; + declEntity = NULL; +@@ -1232,6 +1291,7 @@ XML_ExternalEntityParserCreate(XML_Parse + to worry which hash secrets each table has. + */ + unsigned long oldhash_secret_salt; ++ XML_Bool oldReparseDeferralEnabled; + + /* Validate the oldParser parameter before we pull everything out of it */ + if (oldParser == NULL) +@@ -1276,6 +1336,7 @@ XML_ExternalEntityParserCreate(XML_Parse + to worry which hash secrets each table has. + */ + oldhash_secret_salt = hash_secret_salt; ++ oldReparseDeferralEnabled = reparseDeferralEnabled; + + #ifdef XML_DTD + if (!context) +@@ -1330,6 +1391,7 @@ XML_ExternalEntityParserCreate(XML_Parse + defaultExpandInternalEntities = oldDefaultExpandInternalEntities; + ns_triplets = oldns_triplets; + hash_secret_salt = oldhash_secret_salt; ++ reparseDeferralEnabled = oldReparseDeferralEnabled; + parentParser = oldParser; + #ifdef XML_DTD + paramEntityParsing = oldParamEntityParsing; +@@ -1850,39 +1912,8 @@ XML_Parse(XML_Parser parser, const char + ps_parsing = XML_PARSING; + } + +- if (len == 0) { +- ps_finalBuffer = (XML_Bool)isFinal; +- if (!isFinal) +- return XML_STATUS_OK; +- positionPtr = bufferPtr; +- parseEndPtr = bufferEnd; +- +- /* If data are left over from last buffer, and we now know that these +- data are the final chunk of input, then we have to check them again +- to detect errors based on that fact. +- */ +- errorCode = processor(parser, bufferPtr, parseEndPtr, &bufferPtr); +- +- if (errorCode == XML_ERROR_NONE) { +- switch (ps_parsing) { +- case XML_SUSPENDED: +- XmlUpdatePosition(encoding, positionPtr, bufferPtr, &position); +- positionPtr = bufferPtr; +- return XML_STATUS_SUSPENDED; +- case XML_INITIALIZED: +- case XML_PARSING: +- ps_parsing = XML_FINISHED; +- /* fall through */ +- default: +- return XML_STATUS_OK; +- } +- } +- eventEndPtr = eventPtr; +- processor = errorProcessor; +- return XML_STATUS_ERROR; +- } + #ifndef XML_CONTEXT_BYTES +- else if (bufferPtr == bufferEnd) { ++ if (bufferPtr == bufferEnd) { + const char *end; + int nLeftOver; + enum XML_Status result; +@@ -1899,11 +1930,14 @@ XML_Parse(XML_Parser parser, const char + processor = errorProcessor; + return XML_STATUS_ERROR; + } ++ // though this isn't a buffer request, we assume that `len` is the app's ++ // preferred buffer fill size, and therefore save it here. ++ lastBufferRequestSize = len; + parseEndByteIndex += len; + positionPtr = s; + ps_finalBuffer = (XML_Bool)isFinal; + +- errorCode = processor(parser, s, parseEndPtr = s + len, &end); ++ errorCode = callProcessor(parser, s, parseEndPtr = s + len, &end); + + if (errorCode != XML_ERROR_NONE) { + eventEndPtr = eventPtr; +@@ -1930,6 +1964,8 @@ XML_Parse(XML_Parser parser, const char + XmlUpdatePosition(encoding, positionPtr, end, &position); + nLeftOver = s + len - end; + if (nLeftOver) { ++#if 0 ++// erAck: replace with XML_GetBuffer() below. + if (buffer == NULL || nLeftOver > bufferLim - buffer) { + /* avoid _signed_ integer overflow */ + char *temp = NULL; +@@ -1939,6 +1975,28 @@ XML_Parse(XML_Parser parser, const char + ? (char *)MALLOC(bytesToAllocate) + : (char *)REALLOC(buffer, bytesToAllocate)); + } ++#endif ++#if 1 ++// erAck: the original patch context had a call to XML_GetBuffer() instead: ++ // Back up and restore the parsing status to avoid XML_ERROR_SUSPENDED ++ // (and XML_ERROR_FINISHED) from XML_GetBuffer. ++ const enum XML_Parsing originalStatus = ps_parsing; ++ ps_parsing = XML_PARSING; ++ void *const temp = XML_GetBuffer(parser, nLeftOver); ++ ps_parsing = originalStatus; ++#endif ++ // GetBuffer may have overwritten this, but we want to remember what the ++ // app requested, not how many bytes were left over after parsing. ++ lastBufferRequestSize = len; ++#if 1 ++ if (temp == NULL) { ++ // NOTE: parser->m_errorCode has already been set by XML_GetBuffer(). ++ eventPtr = eventEndPtr = NULL; ++ processor = errorProcessor; ++ return XML_STATUS_ERROR; ++ } ++#endif ++#if 0 + if (temp == NULL) { + errorCode = XML_ERROR_NO_MEMORY; + eventPtr = eventEndPtr = NULL; +@@ -1948,6 +2006,7 @@ XML_Parse(XML_Parser parser, const char + buffer = temp; + bufferLim = buffer + bytesToAllocate; + } ++#endif + memcpy(buffer, end, nLeftOver); + } + bufferPtr = buffer; +@@ -1959,15 +2018,14 @@ XML_Parse(XML_Parser parser, const char + return result; + } + #endif /* not defined XML_CONTEXT_BYTES */ +- else { +- void *buff = XML_GetBuffer(parser, len); +- if (buff == NULL) +- return XML_STATUS_ERROR; +- else { +- memcpy(buff, s, len); +- return XML_ParseBuffer(parser, len, isFinal); +- } ++ void *buff = XML_GetBuffer(parser, len); ++ if (buff == NULL) ++ return XML_STATUS_ERROR; ++ if (len > 0) { ++ assert(s != NULL); // make sure s==NULL && len!=0 was rejected above ++ memcpy(buff, s, len); + } ++ return XML_ParseBuffer(parser, len, isFinal); + } + + enum XML_Status XMLCALL +@@ -2001,7 +2059,7 @@ XML_ParseBuffer(XML_Parser parser, int l + parseEndByteIndex += len; + ps_finalBuffer = (XML_Bool)isFinal; + +- errorCode = processor(parser, start, parseEndPtr, &bufferPtr); ++ errorCode = callProcessor(parser, start, parseEndPtr, &bufferPtr); + + if (errorCode != XML_ERROR_NONE) { + eventEndPtr = eventPtr; +@@ -2047,7 +2105,11 @@ XML_GetBuffer(XML_Parser parser, int len + default: ; + } + +- if (len > bufferLim - bufferEnd) { ++ // whether or not the request succeeds, `len` seems to be the app's preferred ++ // buffer fill size; remember it. ++ lastBufferRequestSize = len; ++ if (len > EXPAT_SAFE_PTR_DIFF(bufferLim, bufferEnd) ++ || buffer == NULL) { + #ifdef XML_CONTEXT_BYTES + int keep; + #endif /* defined XML_CONTEXT_BYTES */ +@@ -2063,7 +2125,9 @@ XML_GetBuffer(XML_Parser parser, int len + keep = XML_CONTEXT_BYTES; + neededSize += keep; + #endif /* defined XML_CONTEXT_BYTES */ +- if (neededSize <= bufferLim - buffer) { ++ if (buffer && bufferPtr ++ && neededSize ++ <= EXPAT_SAFE_PTR_DIFF(bufferLim, buffer)) { + #ifdef XML_CONTEXT_BYTES + if (keep < bufferPtr - buffer) { + int offset = (int)(bufferPtr - buffer) - keep; +@@ -2072,8 +2136,11 @@ XML_GetBuffer(XML_Parser parser, int len + bufferPtr -= offset; + } + #else +- memmove(buffer, bufferPtr, bufferEnd - bufferPtr); +- bufferEnd = buffer + (bufferEnd - bufferPtr); ++ memmove(buffer, bufferPtr, ++ EXPAT_SAFE_PTR_DIFF(bufferEnd, bufferPtr)); ++ bufferEnd ++ = buffer ++ + EXPAT_SAFE_PTR_DIFF(bufferEnd, bufferPtr); + bufferPtr = buffer; + #endif /* not defined XML_CONTEXT_BYTES */ + } +@@ -2171,7 +2238,7 @@ XML_ResumeParser(XML_Parser parser) + } + ps_parsing = XML_PARSING; + +- errorCode = processor(parser, bufferPtr, parseEndPtr, &bufferPtr); ++ errorCode = callProcessor(parser, bufferPtr, parseEndPtr, &bufferPtr); + + if (errorCode != XML_ERROR_NONE) { + eventEndPtr = eventPtr; +@@ -2481,6 +2548,15 @@ MOZ_XML_ProcessingEntityValue(XML_Parser + } + /* END MOZILLA CHANGE */ + ++XML_Bool XMLCALL ++XML_SetReparseDeferralEnabled(XML_Parser parser, XML_Bool enabled) { ++ if (parser != NULL && (enabled == XML_TRUE || enabled == XML_FALSE)) { ++ parser->m_reparseDeferralEnabled = enabled; ++ return XML_TRUE; ++ } ++ return XML_FALSE; ++} ++ + /* Initially tag->rawName always points into the parse buffer; + for those TAG instances opened while the current parse buffer was + processed, and not yet closed, we need to store tag->rawName in a more diff --git a/firefox.spec b/firefox.spec index 0e61fa5..86c3122 100644 --- a/firefox.spec +++ b/firefox.spec @@ -237,6 +237,7 @@ Patch201: firefox-tests-xpcshell-freeze.patch # ---- Security patches ---- Patch301: CVE-2023-44488-libvpx.patch +Patch302: expat-CVE-2023-52425.patch # BUILD REQURES/REQUIRES %if %{?system_nss} && !0%{?bundle_nss} @@ -1047,6 +1048,7 @@ echo "--------------------------------------------" cd media/libvpx/libvpx %patch -P301 -p1 -b .CVE-2023-44488-libvpx cd - +%patch -P302 -p1 -b .expat-CVE-2023-52425 %{__rm} -f .mozconfig %{__cp} %{SOURCE10} .mozconfig