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