summaryrefslogtreecommitdiff
path: root/expat-CVE-2023-52425.patch
diff options
context:
space:
mode:
Diffstat (limited to 'expat-CVE-2023-52425.patch')
-rw-r--r--expat-CVE-2023-52425.patch375
1 files changed, 375 insertions, 0 deletions
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 <stdbool.h>
+ #include <stddef.h>
+ #include <string.h> /* memset(), memcpy() */
+ #include <assert.h>
+@@ -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