summaryrefslogtreecommitdiff
path: root/expat-CVE-2023-52425.patch
blob: bdd06caf5dd4a44a1ab3530223999a3283cce8ed (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
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