summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPeter Marko <peter.marko@siemens.com>2025-03-27 22:44:09 +0100
committerSteve Sakoman <steve@sakoman.com>2025-04-07 06:34:44 -0700
commit5ceb4646d23078dba1395f6512ba81277394cd32 (patch)
tree7db7837792510b2cb3c0da80b1bc87a2fd245cef
parent2af52d4819aea9ca746f0671e2b2f8b5c7de2960 (diff)
downloadpoky-5ceb4646d23078dba1395f6512ba81277394cd32.tar.gz
expat: patch CVE-2024-8176
Backport https://github.com/libexpat/libexpat/pull/973 Patch created by: git diff 2fc36833334340ff7ddca374d86daa8744c1dfa3..99529768b4a722f46c69b04b874c1d45b3eb819c Additional backport (containing changes in tests only) was needed to apply it cleanly. Additional backport https://github.com/libexpat/libexpat/pull/989 which has fixed regression of the first fix. Patch created by: git diff 91ca72e913af94ed44ef2a80a9dd542be3e5766c..308c31ed647f2c6aebe33ca3a4fa9e1436f461e2 (From OE-Core rev: 3ece58813faaf4e5f66c7b52f736e84615ccfef6) Signed-off-by: Peter Marko <peter.marko@siemens.com> Signed-off-by: Steve Sakoman <steve@sakoman.com>
-rw-r--r--meta/recipes-core/expat/expat/0001-tests-Cover-indirect-entity-recursion.patch103
-rw-r--r--meta/recipes-core/expat/expat/CVE-2024-8176-01.patch1477
-rw-r--r--meta/recipes-core/expat/expat/CVE-2024-8176-02.patch248
-rw-r--r--meta/recipes-core/expat/expat_2.6.4.bb3
4 files changed, 1831 insertions, 0 deletions
diff --git a/meta/recipes-core/expat/expat/0001-tests-Cover-indirect-entity-recursion.patch b/meta/recipes-core/expat/expat/0001-tests-Cover-indirect-entity-recursion.patch
new file mode 100644
index 0000000000..802d762787
--- /dev/null
+++ b/meta/recipes-core/expat/expat/0001-tests-Cover-indirect-entity-recursion.patch
@@ -0,0 +1,103 @@
1From 3d5fdbb44e80ed789e4f6510542d77d6284fbd0e Mon Sep 17 00:00:00 2001
2From: Sebastian Pipping <sebastian@pipping.org>
3Date: Sat, 23 Nov 2024 14:20:21 +0100
4Subject: [PATCH] tests: Cover indirect entity recursion
5
6Upstream-Status: Backport [https://github.com/libexpat/libexpat/commit/3d5fdbb44e80ed789e4f6510542d77d6284fbd0e]
7Signed-off-by: Peter Marko <peter.marko@siemens.com>
8---
9 expat/tests/basic_tests.c | 74 +++++++++++++++++++++++++++++++++++++++
10 1 file changed, 74 insertions(+)
11
12diff --git a/expat/tests/basic_tests.c b/expat/tests/basic_tests.c
13index d38b8fd1..d2306772 100644
14--- a/expat/tests/basic_tests.c
15+++ b/expat/tests/basic_tests.c
16@@ -1202,6 +1202,79 @@ START_TEST(test_wfc_no_recursive_entity_refs) {
17 }
18 END_TEST
19
20+START_TEST(test_no_indirectly_recursive_entity_refs) {
21+ struct TestCase {
22+ const char *doc;
23+ bool usesParameterEntities;
24+ };
25+
26+ const struct TestCase cases[] = {
27+ // general entity + character data
28+ {"<!DOCTYPE a [\n"
29+ " <!ENTITY e1 '&e2;'>\n"
30+ " <!ENTITY e2 '&e1;'>\n"
31+ "]><a>&e2;</a>\n",
32+ false},
33+
34+ // general entity + attribute value
35+ {"<!DOCTYPE a [\n"
36+ " <!ENTITY e1 '&e2;'>\n"
37+ " <!ENTITY e2 '&e1;'>\n"
38+ "]><a k1='&e2;' />\n",
39+ false},
40+
41+ // parameter entity
42+ {"<!DOCTYPE doc [\n"
43+ " <!ENTITY % p1 '&#37;p2;'>\n"
44+ " <!ENTITY % p2 '&#37;p1;'>\n"
45+ " <!ENTITY % define_g \"<!ENTITY g '&#37;p2;'>\">\n"
46+ " %define_g;\n"
47+ "]>\n"
48+ "<doc/>\n",
49+ true},
50+ };
51+ for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
52+ const char *const doc = cases[i].doc;
53+ const bool usesParameterEntities = cases[i].usesParameterEntities;
54+
55+ set_subtest("[%i] %s", (int)i, doc);
56+
57+#ifdef XML_DTD // both GE and DTD
58+ const bool rejection_expected = true;
59+#elif XML_GE == 1 // GE but not DTD
60+ const bool rejection_expected = ! usesParameterEntities;
61+#else // neither DTD nor GE
62+ const bool rejection_expected = false;
63+#endif
64+
65+ XML_Parser parser = XML_ParserCreate(NULL);
66+
67+#ifdef XML_DTD
68+ if (usesParameterEntities) {
69+ assert_true(
70+ XML_SetParamEntityParsing(parser, XML_PARAM_ENTITY_PARSING_ALWAYS)
71+ == 1);
72+ }
73+#else
74+ UNUSED_P(usesParameterEntities);
75+#endif // XML_DTD
76+
77+ const enum XML_Status status
78+ = _XML_Parse_SINGLE_BYTES(parser, doc, (int)strlen(doc),
79+ /*isFinal*/ XML_TRUE);
80+
81+ if (rejection_expected) {
82+ assert_true(status == XML_STATUS_ERROR);
83+ assert_true(XML_GetErrorCode(parser) == XML_ERROR_RECURSIVE_ENTITY_REF);
84+ } else {
85+ assert_true(status == XML_STATUS_OK);
86+ }
87+
88+ XML_ParserFree(parser);
89+ }
90+}
91+END_TEST
92+
93 START_TEST(test_recursive_external_parameter_entity_2) {
94 struct TestCase {
95 const char *doc;
96@@ -5969,6 +6042,7 @@ make_basic_test_case(Suite *s) {
97 tcase_add_test(tc_basic, test_not_standalone_handler_reject);
98 tcase_add_test(tc_basic, test_not_standalone_handler_accept);
99 tcase_add_test__if_xml_ge(tc_basic, test_wfc_no_recursive_entity_refs);
100+ tcase_add_test(tc_basic, test_no_indirectly_recursive_entity_refs);
101 tcase_add_test__ifdef_xml_dtd(tc_basic, test_ext_entity_invalid_parse);
102 tcase_add_test__if_xml_ge(tc_basic, test_dtd_default_handling);
103 tcase_add_test(tc_basic, test_dtd_attr_handling);
diff --git a/meta/recipes-core/expat/expat/CVE-2024-8176-01.patch b/meta/recipes-core/expat/expat/CVE-2024-8176-01.patch
new file mode 100644
index 0000000000..dc8a520161
--- /dev/null
+++ b/meta/recipes-core/expat/expat/CVE-2024-8176-01.patch
@@ -0,0 +1,1477 @@
1From 3f924a715cfa97e70df1c24334d2d728973d1020 Mon Sep 17 00:00:00 2001
2From: Peter Marko <peter.marko@siemens.com>
3Date: Mon, 17 Mar 2025 20:41:24 +0100
4Subject: [PATCH] [CVE-2024-8176] Resolve the recursion during entity
5 processing to prevent stack overflow (fixes #893)
6
7Fixes #893
8
9CVE: CVE-2024-8176
10Upstream-Status: Backport [https://github.com/libexpat/libexpat/pull/973]
11Signed-off-by: Peter Marko <peter.marko@siemens.com>
12---
13 expat/Changes | 29 +-
14 expat/lib/xmlparse.c | 564 ++++++++++++++++++++++++++++----------
15 expat/tests/alloc_tests.c | 27 ++
16 expat/tests/basic_tests.c | 247 +++++++++++++++--
17 expat/tests/handlers.c | 14 +
18 expat/tests/handlers.h | 5 +
19 expat/tests/misc_tests.c | 43 +++
20 7 files changed, 751 insertions(+), 178 deletions(-)
21
22diff --git a/expat/Changes b/expat/Changes
23index aa19f70a..8c5db88c 100644
24--- a/expat/Changes
25+++ b/expat/Changes
26@@ -11,7 +11,6 @@
27 !! The following topics need *additional skilled C developers* to progress !!
28 !! in a timely manner or at all (loosely ordered by descending priority): !!
29 !! !!
30-!! - <blink>fixing a complex non-public security issue</blink>, !!
31 !! - teaming up on researching and fixing future security reports and !!
32 !! ClusterFuzz findings with few-days-max response times in communication !!
33 !! in order to (1) have a sound fix ready before the end of a 90 days !!
34@@ -30,6 +29,34 @@
35 !! THANK YOU! Sebastian Pipping -- Berlin, 2024-03-09 !!
36 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
37
38+Patches:
39+ Security fixes:
40+ #893 #??? CVE-2024-8176 -- Fix crash from chaining a large number
41+ of entities caused by stack overflow by resolving use of
42+ recursion, for all three uses of entities:
43+ - general entities in character data ("<e>&g1;</e>")
44+ - general entities in attribute values ("<e k1='&g1;'/>")
45+ - parameter entities ("%p1;")
46+ Known impact is (reliable and easy) denial of service:
47+ CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H/E:H/RL:O/RC:C
48+ (Base Score: 7.5, Temporal Score: 7.2)
49+ Please note that a layer of compression around XML can
50+ significantly reduce the minimum attack payload size.
51+
52+ Special thanks to:
53+ Alexander Gieringer
54+ Berkay Eren Ürün
55+ Jann Horn
56+ Sebastian Andrzej Siewior
57+ Snild Dolkow
58+ Thomas Pröll
59+ Tomas Korbar
60+ and
61+ Google Project Zero
62+ Linutronix
63+ Red Hat
64+ Siemens
65+
66 Release 2.6.4 Wed November 6 2024
67 Security fixes:
68 #915 CVE-2024-50602 -- Fix crash within function XML_ResumeParser
69diff --git a/expat/lib/xmlparse.c b/expat/lib/xmlparse.c
70index a4e091e7..473c791d 100644
71--- a/expat/lib/xmlparse.c
72+++ b/expat/lib/xmlparse.c
73@@ -39,7 +39,7 @@
74 Copyright (c) 2022 Sean McBride <sean@rogue-research.com>
75 Copyright (c) 2023 Owain Davies <owaind@bath.edu>
76 Copyright (c) 2023-2024 Sony Corporation / Snild Dolkow <snild@sony.com>
77- Copyright (c) 2024 Berkay Eren Ürün <berkay.ueruen@siemens.com>
78+ Copyright (c) 2024-2025 Berkay Eren Ürün <berkay.ueruen@siemens.com>
79 Copyright (c) 2024 Hanno Böck <hanno@gentoo.org>
80 Licensed under the MIT license:
81
82@@ -325,6 +325,10 @@ typedef struct {
83 const XML_Char *publicId;
84 const XML_Char *notation;
85 XML_Bool open;
86+ XML_Bool hasMore; /* true if entity has not been completely processed */
87+ /* An entity can be open while being already completely processed (hasMore ==
88+ XML_FALSE). The reason is the delayed closing of entities until their inner
89+ entities are processed and closed */
90 XML_Bool is_param;
91 XML_Bool is_internal; /* true if declared in internal subset outside PE */
92 } ENTITY;
93@@ -415,6 +419,12 @@ typedef struct {
94 int *scaffIndex;
95 } DTD;
96
97+enum EntityType {
98+ ENTITY_INTERNAL,
99+ ENTITY_ATTRIBUTE,
100+ ENTITY_VALUE,
101+};
102+
103 typedef struct open_internal_entity {
104 const char *internalEventPtr;
105 const char *internalEventEndPtr;
106@@ -422,6 +432,7 @@ typedef struct open_internal_entity {
107 ENTITY *entity;
108 int startTagLevel;
109 XML_Bool betweenDecl; /* WFC: PE Between Declarations */
110+ enum EntityType type;
111 } OPEN_INTERNAL_ENTITY;
112
113 enum XML_Account {
114@@ -481,8 +492,8 @@ static enum XML_Error doProlog(XML_Parser parser, const ENCODING *enc,
115 const char *next, const char **nextPtr,
116 XML_Bool haveMore, XML_Bool allowClosingDoctype,
117 enum XML_Account account);
118-static enum XML_Error processInternalEntity(XML_Parser parser, ENTITY *entity,
119- XML_Bool betweenDecl);
120+static enum XML_Error processEntity(XML_Parser parser, ENTITY *entity,
121+ XML_Bool betweenDecl, enum EntityType type);
122 static enum XML_Error doContent(XML_Parser parser, int startTagLevel,
123 const ENCODING *enc, const char *start,
124 const char *end, const char **endPtr,
125@@ -513,18 +524,22 @@ static enum XML_Error storeAttributeValue(XML_Parser parser,
126 const char *ptr, const char *end,
127 STRING_POOL *pool,
128 enum XML_Account account);
129-static enum XML_Error appendAttributeValue(XML_Parser parser,
130- const ENCODING *enc,
131- XML_Bool isCdata, const char *ptr,
132- const char *end, STRING_POOL *pool,
133- enum XML_Account account);
134+static enum XML_Error
135+appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata,
136+ const char *ptr, const char *end, STRING_POOL *pool,
137+ enum XML_Account account, const char **nextPtr);
138 static ATTRIBUTE_ID *getAttributeId(XML_Parser parser, const ENCODING *enc,
139 const char *start, const char *end);
140 static int setElementTypePrefix(XML_Parser parser, ELEMENT_TYPE *elementType);
141 #if XML_GE == 1
142 static enum XML_Error storeEntityValue(XML_Parser parser, const ENCODING *enc,
143 const char *start, const char *end,
144- enum XML_Account account);
145+ enum XML_Account account,
146+ const char **nextPtr);
147+static enum XML_Error callStoreEntityValue(XML_Parser parser,
148+ const ENCODING *enc,
149+ const char *start, const char *end,
150+ enum XML_Account account);
151 #else
152 static enum XML_Error storeSelfEntityValue(XML_Parser parser, ENTITY *entity);
153 #endif
154@@ -709,6 +724,10 @@ struct XML_ParserStruct {
155 const char *m_positionPtr;
156 OPEN_INTERNAL_ENTITY *m_openInternalEntities;
157 OPEN_INTERNAL_ENTITY *m_freeInternalEntities;
158+ OPEN_INTERNAL_ENTITY *m_openAttributeEntities;
159+ OPEN_INTERNAL_ENTITY *m_freeAttributeEntities;
160+ OPEN_INTERNAL_ENTITY *m_openValueEntities;
161+ OPEN_INTERNAL_ENTITY *m_freeValueEntities;
162 XML_Bool m_defaultExpandInternalEntities;
163 int m_tagLevel;
164 ENTITY *m_declEntity;
165@@ -756,6 +775,7 @@ struct XML_ParserStruct {
166 ACCOUNTING m_accounting;
167 ENTITY_STATS m_entity_stats;
168 #endif
169+ XML_Bool m_reenter;
170 };
171
172 #define MALLOC(parser, s) (parser->m_mem.malloc_fcn((s)))
173@@ -1028,7 +1048,29 @@ callProcessor(XML_Parser parser, const char *start, const char *end,
174 #if defined(XML_TESTING)
175 g_bytesScanned += (unsigned)have_now;
176 #endif
177- const enum XML_Error ret = parser->m_processor(parser, start, end, endPtr);
178+ // Run in a loop to eliminate dangerous recursion depths
179+ enum XML_Error ret;
180+ *endPtr = start;
181+ while (1) {
182+ // Use endPtr as the new start in each iteration, since it will
183+ // be set to the next start point by m_processor.
184+ ret = parser->m_processor(parser, *endPtr, end, endPtr);
185+
186+ // Make parsing status (and in particular XML_SUSPENDED) take
187+ // precedence over re-enter flag when they disagree
188+ if (parser->m_parsingStatus.parsing != XML_PARSING) {
189+ parser->m_reenter = XML_FALSE;
190+ }
191+
192+ if (! parser->m_reenter) {
193+ break;
194+ }
195+
196+ parser->m_reenter = XML_FALSE;
197+ if (ret != XML_ERROR_NONE)
198+ return ret;
199+ }
200+
201 if (ret == XML_ERROR_NONE) {
202 // if we consumed nothing, remember what we had on this parse attempt.
203 if (*endPtr == start) {
204@@ -1139,6 +1181,8 @@ parserCreate(const XML_Char *encodingName,
205 parser->m_freeBindingList = NULL;
206 parser->m_freeTagList = NULL;
207 parser->m_freeInternalEntities = NULL;
208+ parser->m_freeAttributeEntities = NULL;
209+ parser->m_freeValueEntities = NULL;
210
211 parser->m_groupSize = 0;
212 parser->m_groupConnector = NULL;
213@@ -1241,6 +1285,8 @@ parserInit(XML_Parser parser, const XML_Char *encodingName) {
214 parser->m_eventEndPtr = NULL;
215 parser->m_positionPtr = NULL;
216 parser->m_openInternalEntities = NULL;
217+ parser->m_openAttributeEntities = NULL;
218+ parser->m_openValueEntities = NULL;
219 parser->m_defaultExpandInternalEntities = XML_TRUE;
220 parser->m_tagLevel = 0;
221 parser->m_tagStack = NULL;
222@@ -1251,6 +1297,8 @@ parserInit(XML_Parser parser, const XML_Char *encodingName) {
223 parser->m_unknownEncodingData = NULL;
224 parser->m_parentParser = NULL;
225 parser->m_parsingStatus.parsing = XML_INITIALIZED;
226+ // Reentry can only be triggered inside m_processor calls
227+ parser->m_reenter = XML_FALSE;
228 #ifdef XML_DTD
229 parser->m_isParamEntity = XML_FALSE;
230 parser->m_useForeignDTD = XML_FALSE;
231@@ -1310,6 +1358,24 @@ XML_ParserReset(XML_Parser parser, const XML_Char *encodingName) {
232 openEntity->next = parser->m_freeInternalEntities;
233 parser->m_freeInternalEntities = openEntity;
234 }
235+ /* move m_openAttributeEntities to m_freeAttributeEntities (i.e. same task but
236+ * for attributes) */
237+ openEntityList = parser->m_openAttributeEntities;
238+ while (openEntityList) {
239+ OPEN_INTERNAL_ENTITY *openEntity = openEntityList;
240+ openEntityList = openEntity->next;
241+ openEntity->next = parser->m_freeAttributeEntities;
242+ parser->m_freeAttributeEntities = openEntity;
243+ }
244+ /* move m_openValueEntities to m_freeValueEntities (i.e. same task but
245+ * for value entities) */
246+ openEntityList = parser->m_openValueEntities;
247+ while (openEntityList) {
248+ OPEN_INTERNAL_ENTITY *openEntity = openEntityList;
249+ openEntityList = openEntity->next;
250+ openEntity->next = parser->m_freeValueEntities;
251+ parser->m_freeValueEntities = openEntity;
252+ }
253 moveToFreeBindingList(parser, parser->m_inheritedBindings);
254 FREE(parser, parser->m_unknownEncodingMem);
255 if (parser->m_unknownEncodingRelease)
256@@ -1323,6 +1389,19 @@ XML_ParserReset(XML_Parser parser, const XML_Char *encodingName) {
257 return XML_TRUE;
258 }
259
260+static XML_Bool
261+parserBusy(XML_Parser parser) {
262+ switch (parser->m_parsingStatus.parsing) {
263+ case XML_PARSING:
264+ case XML_SUSPENDED:
265+ return XML_TRUE;
266+ case XML_INITIALIZED:
267+ case XML_FINISHED:
268+ default:
269+ return XML_FALSE;
270+ }
271+}
272+
273 enum XML_Status XMLCALL
274 XML_SetEncoding(XML_Parser parser, const XML_Char *encodingName) {
275 if (parser == NULL)
276@@ -1331,8 +1410,7 @@ XML_SetEncoding(XML_Parser parser, const XML_Char *encodingName) {
277 XXX There's no way for the caller to determine which of the
278 XXX possible error cases caused the XML_STATUS_ERROR return.
279 */
280- if (parser->m_parsingStatus.parsing == XML_PARSING
281- || parser->m_parsingStatus.parsing == XML_SUSPENDED)
282+ if (parserBusy(parser))
283 return XML_STATUS_ERROR;
284
285 /* Get rid of any previous encoding name */
286@@ -1569,7 +1647,34 @@ XML_ParserFree(XML_Parser parser) {
287 entityList = entityList->next;
288 FREE(parser, openEntity);
289 }
290-
291+ /* free m_openAttributeEntities and m_freeAttributeEntities */
292+ entityList = parser->m_openAttributeEntities;
293+ for (;;) {
294+ OPEN_INTERNAL_ENTITY *openEntity;
295+ if (entityList == NULL) {
296+ if (parser->m_freeAttributeEntities == NULL)
297+ break;
298+ entityList = parser->m_freeAttributeEntities;
299+ parser->m_freeAttributeEntities = NULL;
300+ }
301+ openEntity = entityList;
302+ entityList = entityList->next;
303+ FREE(parser, openEntity);
304+ }
305+ /* free m_openValueEntities and m_freeValueEntities */
306+ entityList = parser->m_openValueEntities;
307+ for (;;) {
308+ OPEN_INTERNAL_ENTITY *openEntity;
309+ if (entityList == NULL) {
310+ if (parser->m_freeValueEntities == NULL)
311+ break;
312+ entityList = parser->m_freeValueEntities;
313+ parser->m_freeValueEntities = NULL;
314+ }
315+ openEntity = entityList;
316+ entityList = entityList->next;
317+ FREE(parser, openEntity);
318+ }
319 destroyBindings(parser->m_freeBindingList, parser);
320 destroyBindings(parser->m_inheritedBindings, parser);
321 poolDestroy(&parser->m_tempPool);
322@@ -1611,8 +1716,7 @@ XML_UseForeignDTD(XML_Parser parser, XML_Bool useDTD) {
323 return XML_ERROR_INVALID_ARGUMENT;
324 #ifdef XML_DTD
325 /* block after XML_Parse()/XML_ParseBuffer() has been called */
326- if (parser->m_parsingStatus.parsing == XML_PARSING
327- || parser->m_parsingStatus.parsing == XML_SUSPENDED)
328+ if (parserBusy(parser))
329 return XML_ERROR_CANT_CHANGE_FEATURE_ONCE_PARSING;
330 parser->m_useForeignDTD = useDTD;
331 return XML_ERROR_NONE;
332@@ -1627,8 +1731,7 @@ XML_SetReturnNSTriplet(XML_Parser parser, int do_nst) {
333 if (parser == NULL)
334 return;
335 /* block after XML_Parse()/XML_ParseBuffer() has been called */
336- if (parser->m_parsingStatus.parsing == XML_PARSING
337- || parser->m_parsingStatus.parsing == XML_SUSPENDED)
338+ if (parserBusy(parser))
339 return;
340 parser->m_ns_triplets = do_nst ? XML_TRUE : XML_FALSE;
341 }
342@@ -1897,8 +2000,7 @@ XML_SetParamEntityParsing(XML_Parser parser,
343 if (parser == NULL)
344 return 0;
345 /* block after XML_Parse()/XML_ParseBuffer() has been called */
346- if (parser->m_parsingStatus.parsing == XML_PARSING
347- || parser->m_parsingStatus.parsing == XML_SUSPENDED)
348+ if (parserBusy(parser))
349 return 0;
350 #ifdef XML_DTD
351 parser->m_paramEntityParsing = peParsing;
352@@ -1915,8 +2017,7 @@ XML_SetHashSalt(XML_Parser parser, unsigned long hash_salt) {
353 if (parser->m_parentParser)
354 return XML_SetHashSalt(parser->m_parentParser, hash_salt);
355 /* block after XML_Parse()/XML_ParseBuffer() has been called */
356- if (parser->m_parsingStatus.parsing == XML_PARSING
357- || parser->m_parsingStatus.parsing == XML_SUSPENDED)
358+ if (parserBusy(parser))
359 return 0;
360 parser->m_hash_secret_salt = hash_salt;
361 return 1;
362@@ -2230,6 +2331,11 @@ XML_GetBuffer(XML_Parser parser, int len) {
363 return parser->m_bufferEnd;
364 }
365
366+static void
367+triggerReenter(XML_Parser parser) {
368+ parser->m_reenter = XML_TRUE;
369+}
370+
371 enum XML_Status XMLCALL
372 XML_StopParser(XML_Parser parser, XML_Bool resumable) {
373 if (parser == NULL)
374@@ -2704,8 +2810,9 @@ static enum XML_Error PTRCALL
375 contentProcessor(XML_Parser parser, const char *start, const char *end,
376 const char **endPtr) {
377 enum XML_Error result = doContent(
378- parser, 0, parser->m_encoding, start, end, endPtr,
379- (XML_Bool)! parser->m_parsingStatus.finalBuffer, XML_ACCOUNT_DIRECT);
380+ parser, parser->m_parentParser ? 1 : 0, parser->m_encoding, start, end,
381+ endPtr, (XML_Bool)! parser->m_parsingStatus.finalBuffer,
382+ XML_ACCOUNT_DIRECT);
383 if (result == XML_ERROR_NONE) {
384 if (! storeRawNames(parser))
385 return XML_ERROR_NO_MEMORY;
386@@ -2793,6 +2900,11 @@ externalEntityInitProcessor3(XML_Parser parser, const char *start,
387 return XML_ERROR_NONE;
388 case XML_FINISHED:
389 return XML_ERROR_ABORTED;
390+ case XML_PARSING:
391+ if (parser->m_reenter) {
392+ return XML_ERROR_UNEXPECTED_STATE; // LCOV_EXCL_LINE
393+ }
394+ /* Fall through */
395 default:
396 start = next;
397 }
398@@ -2966,7 +3078,7 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc,
399 reportDefault(parser, enc, s, next);
400 break;
401 }
402- result = processInternalEntity(parser, entity, XML_FALSE);
403+ result = processEntity(parser, entity, XML_FALSE, ENTITY_INTERNAL);
404 if (result != XML_ERROR_NONE)
405 return result;
406 } else if (parser->m_externalEntityRefHandler) {
407@@ -3092,7 +3204,9 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc,
408 }
409 if ((parser->m_tagLevel == 0)
410 && (parser->m_parsingStatus.parsing != XML_FINISHED)) {
411- if (parser->m_parsingStatus.parsing == XML_SUSPENDED)
412+ if (parser->m_parsingStatus.parsing == XML_SUSPENDED
413+ || (parser->m_parsingStatus.parsing == XML_PARSING
414+ && parser->m_reenter))
415 parser->m_processor = epilogProcessor;
416 else
417 return epilogProcessor(parser, next, end, nextPtr);
418@@ -3153,7 +3267,9 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc,
419 }
420 if ((parser->m_tagLevel == 0)
421 && (parser->m_parsingStatus.parsing != XML_FINISHED)) {
422- if (parser->m_parsingStatus.parsing == XML_SUSPENDED)
423+ if (parser->m_parsingStatus.parsing == XML_SUSPENDED
424+ || (parser->m_parsingStatus.parsing == XML_PARSING
425+ && parser->m_reenter))
426 parser->m_processor = epilogProcessor;
427 else
428 return epilogProcessor(parser, next, end, nextPtr);
429@@ -3293,6 +3409,12 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc,
430 return XML_ERROR_NONE;
431 case XML_FINISHED:
432 return XML_ERROR_ABORTED;
433+ case XML_PARSING:
434+ if (parser->m_reenter) {
435+ *nextPtr = next;
436+ return XML_ERROR_NONE;
437+ }
438+ /* Fall through */
439 default:;
440 }
441 }
442@@ -4217,6 +4339,11 @@ doCdataSection(XML_Parser parser, const ENCODING *enc, const char **startPtr,
443 return XML_ERROR_NONE;
444 case XML_FINISHED:
445 return XML_ERROR_ABORTED;
446+ case XML_PARSING:
447+ if (parser->m_reenter) {
448+ return XML_ERROR_UNEXPECTED_STATE; // LCOV_EXCL_LINE
449+ }
450+ /* Fall through */
451 default:;
452 }
453 }
454@@ -4549,7 +4676,7 @@ entityValueInitProcessor(XML_Parser parser, const char *s, const char *end,
455 }
456 /* found end of entity value - can store it now */
457 return storeEntityValue(parser, parser->m_encoding, s, end,
458- XML_ACCOUNT_DIRECT);
459+ XML_ACCOUNT_DIRECT, NULL);
460 } else if (tok == XML_TOK_XML_DECL) {
461 enum XML_Error result;
462 result = processXmlDecl(parser, 0, start, next);
463@@ -4676,7 +4803,7 @@ entityValueProcessor(XML_Parser parser, const char *s, const char *end,
464 break;
465 }
466 /* found end of entity value - can store it now */
467- return storeEntityValue(parser, enc, s, end, XML_ACCOUNT_DIRECT);
468+ return storeEntityValue(parser, enc, s, end, XML_ACCOUNT_DIRECT, NULL);
469 }
470 start = next;
471 }
472@@ -5119,9 +5246,9 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end,
473 #if XML_GE == 1
474 // This will store the given replacement text in
475 // parser->m_declEntity->textPtr.
476- enum XML_Error result
477- = storeEntityValue(parser, enc, s + enc->minBytesPerChar,
478- next - enc->minBytesPerChar, XML_ACCOUNT_NONE);
479+ enum XML_Error result = callStoreEntityValue(
480+ parser, enc, s + enc->minBytesPerChar, next - enc->minBytesPerChar,
481+ XML_ACCOUNT_NONE);
482 if (parser->m_declEntity) {
483 parser->m_declEntity->textPtr = poolStart(&dtd->entityValuePool);
484 parser->m_declEntity->textLen
485@@ -5546,7 +5673,7 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end,
486 enum XML_Error result;
487 XML_Bool betweenDecl
488 = (role == XML_ROLE_PARAM_ENTITY_REF ? XML_TRUE : XML_FALSE);
489- result = processInternalEntity(parser, entity, betweenDecl);
490+ result = processEntity(parser, entity, betweenDecl, ENTITY_INTERNAL);
491 if (result != XML_ERROR_NONE)
492 return result;
493 handleDefault = XML_FALSE;
494@@ -5751,6 +5878,12 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end,
495 return XML_ERROR_NONE;
496 case XML_FINISHED:
497 return XML_ERROR_ABORTED;
498+ case XML_PARSING:
499+ if (parser->m_reenter) {
500+ *nextPtr = next;
501+ return XML_ERROR_NONE;
502+ }
503+ /* Fall through */
504 default:
505 s = next;
506 tok = XmlPrologTok(enc, s, end, &next);
507@@ -5825,21 +5958,49 @@ epilogProcessor(XML_Parser parser, const char *s, const char *end,
508 return XML_ERROR_NONE;
509 case XML_FINISHED:
510 return XML_ERROR_ABORTED;
511+ case XML_PARSING:
512+ if (parser->m_reenter) {
513+ return XML_ERROR_UNEXPECTED_STATE; // LCOV_EXCL_LINE
514+ }
515+ /* Fall through */
516 default:;
517 }
518 }
519 }
520
521 static enum XML_Error
522-processInternalEntity(XML_Parser parser, ENTITY *entity, XML_Bool betweenDecl) {
523- const char *textStart, *textEnd;
524- const char *next;
525- enum XML_Error result;
526- OPEN_INTERNAL_ENTITY *openEntity;
527+processEntity(XML_Parser parser, ENTITY *entity, XML_Bool betweenDecl,
528+ enum EntityType type) {
529+ OPEN_INTERNAL_ENTITY *openEntity, **openEntityList, **freeEntityList;
530+ switch (type) {
531+ case ENTITY_INTERNAL:
532+ parser->m_processor = internalEntityProcessor;
533+ openEntityList = &parser->m_openInternalEntities;
534+ freeEntityList = &parser->m_freeInternalEntities;
535+ break;
536+ case ENTITY_ATTRIBUTE:
537+ openEntityList = &parser->m_openAttributeEntities;
538+ freeEntityList = &parser->m_freeAttributeEntities;
539+ break;
540+ case ENTITY_VALUE:
541+ openEntityList = &parser->m_openValueEntities;
542+ freeEntityList = &parser->m_freeValueEntities;
543+ break;
544+ /* default case serves merely as a safety net in case of a
545+ * wrong entityType. Therefore we exclude the following lines
546+ * from the test coverage.
547+ *
548+ * LCOV_EXCL_START
549+ */
550+ default:
551+ // Should not reach here
552+ assert(0);
553+ /* LCOV_EXCL_STOP */
554+ }
555
556- if (parser->m_freeInternalEntities) {
557- openEntity = parser->m_freeInternalEntities;
558- parser->m_freeInternalEntities = openEntity->next;
559+ if (*freeEntityList) {
560+ openEntity = *freeEntityList;
561+ *freeEntityList = openEntity->next;
562 } else {
563 openEntity
564 = (OPEN_INTERNAL_ENTITY *)MALLOC(parser, sizeof(OPEN_INTERNAL_ENTITY));
565@@ -5847,55 +6008,34 @@ processInternalEntity(XML_Parser parser, ENTITY *entity, XML_Bool betweenDecl) {
566 return XML_ERROR_NO_MEMORY;
567 }
568 entity->open = XML_TRUE;
569+ entity->hasMore = XML_TRUE;
570 #if XML_GE == 1
571 entityTrackingOnOpen(parser, entity, __LINE__);
572 #endif
573 entity->processed = 0;
574- openEntity->next = parser->m_openInternalEntities;
575- parser->m_openInternalEntities = openEntity;
576+ openEntity->next = *openEntityList;
577+ *openEntityList = openEntity;
578 openEntity->entity = entity;
579+ openEntity->type = type;
580 openEntity->startTagLevel = parser->m_tagLevel;
581 openEntity->betweenDecl = betweenDecl;
582 openEntity->internalEventPtr = NULL;
583 openEntity->internalEventEndPtr = NULL;
584- textStart = (const char *)entity->textPtr;
585- textEnd = (const char *)(entity->textPtr + entity->textLen);
586- /* Set a safe default value in case 'next' does not get set */
587- next = textStart;
588
589- if (entity->is_param) {
590- int tok
591- = XmlPrologTok(parser->m_internalEncoding, textStart, textEnd, &next);
592- result = doProlog(parser, parser->m_internalEncoding, textStart, textEnd,
593- tok, next, &next, XML_FALSE, XML_FALSE,
594- XML_ACCOUNT_ENTITY_EXPANSION);
595- } else {
596- result = doContent(parser, parser->m_tagLevel, parser->m_internalEncoding,
597- textStart, textEnd, &next, XML_FALSE,
598- XML_ACCOUNT_ENTITY_EXPANSION);
599+ // Only internal entities make use of the reenter flag
600+ // therefore no need to set it for other entity types
601+ if (type == ENTITY_INTERNAL) {
602+ triggerReenter(parser);
603 }
604-
605- if (result == XML_ERROR_NONE) {
606- if (textEnd != next && parser->m_parsingStatus.parsing == XML_SUSPENDED) {
607- entity->processed = (int)(next - textStart);
608- parser->m_processor = internalEntityProcessor;
609- } else if (parser->m_openInternalEntities->entity == entity) {
610-#if XML_GE == 1
611- entityTrackingOnClose(parser, entity, __LINE__);
612-#endif /* XML_GE == 1 */
613- entity->open = XML_FALSE;
614- parser->m_openInternalEntities = openEntity->next;
615- /* put openEntity back in list of free instances */
616- openEntity->next = parser->m_freeInternalEntities;
617- parser->m_freeInternalEntities = openEntity;
618- }
619- }
620- return result;
621+ return XML_ERROR_NONE;
622 }
623
624 static enum XML_Error PTRCALL
625 internalEntityProcessor(XML_Parser parser, const char *s, const char *end,
626 const char **nextPtr) {
627+ UNUSED_P(s);
628+ UNUSED_P(end);
629+ UNUSED_P(nextPtr);
630 ENTITY *entity;
631 const char *textStart, *textEnd;
632 const char *next;
633@@ -5905,68 +6045,67 @@ internalEntityProcessor(XML_Parser parser, const char *s, const char *end,
634 return XML_ERROR_UNEXPECTED_STATE;
635
636 entity = openEntity->entity;
637- textStart = ((const char *)entity->textPtr) + entity->processed;
638- textEnd = (const char *)(entity->textPtr + entity->textLen);
639- /* Set a safe default value in case 'next' does not get set */
640- next = textStart;
641
642- if (entity->is_param) {
643- int tok
644- = XmlPrologTok(parser->m_internalEncoding, textStart, textEnd, &next);
645- result = doProlog(parser, parser->m_internalEncoding, textStart, textEnd,
646- tok, next, &next, XML_FALSE, XML_TRUE,
647- XML_ACCOUNT_ENTITY_EXPANSION);
648- } else {
649- result = doContent(parser, openEntity->startTagLevel,
650- parser->m_internalEncoding, textStart, textEnd, &next,
651- XML_FALSE, XML_ACCOUNT_ENTITY_EXPANSION);
652- }
653+ // This will return early
654+ if (entity->hasMore) {
655+ textStart = ((const char *)entity->textPtr) + entity->processed;
656+ textEnd = (const char *)(entity->textPtr + entity->textLen);
657+ /* Set a safe default value in case 'next' does not get set */
658+ next = textStart;
659
660- if (result != XML_ERROR_NONE)
661- return result;
662+ if (entity->is_param) {
663+ int tok
664+ = XmlPrologTok(parser->m_internalEncoding, textStart, textEnd, &next);
665+ result = doProlog(parser, parser->m_internalEncoding, textStart, textEnd,
666+ tok, next, &next, XML_FALSE, XML_FALSE,
667+ XML_ACCOUNT_ENTITY_EXPANSION);
668+ } else {
669+ result = doContent(parser, openEntity->startTagLevel,
670+ parser->m_internalEncoding, textStart, textEnd, &next,
671+ XML_FALSE, XML_ACCOUNT_ENTITY_EXPANSION);
672+ }
673+
674+ if (result != XML_ERROR_NONE)
675+ return result;
676+ // Check if entity is complete, if not, mark down how much of it is
677+ // processed
678+ if (textEnd != next
679+ && (parser->m_parsingStatus.parsing == XML_SUSPENDED
680+ || (parser->m_parsingStatus.parsing == XML_PARSING
681+ && parser->m_reenter))) {
682+ entity->processed = (int)(next - (const char *)entity->textPtr);
683+ return result;
684+ }
685
686- if (textEnd != next && parser->m_parsingStatus.parsing == XML_SUSPENDED) {
687- entity->processed = (int)(next - (const char *)entity->textPtr);
688+ // Entity is complete. We cannot close it here since we need to first
689+ // process its possible inner entities (which are added to the
690+ // m_openInternalEntities during doProlog or doContent calls above)
691+ entity->hasMore = XML_FALSE;
692+ triggerReenter(parser);
693 return result;
694- }
695+ } // End of entity processing, "if" block will return here
696
697+ // Remove fully processed openEntity from open entity list.
698 #if XML_GE == 1
699 entityTrackingOnClose(parser, entity, __LINE__);
700 #endif
701+ // openEntity is m_openInternalEntities' head, as we set it at the start of
702+ // this function and we skipped doProlog and doContent calls with hasMore set
703+ // to false. This means we can directly remove the head of
704+ // m_openInternalEntities
705+ assert(parser->m_openInternalEntities == openEntity);
706 entity->open = XML_FALSE;
707- parser->m_openInternalEntities = openEntity->next;
708+ parser->m_openInternalEntities = parser->m_openInternalEntities->next;
709+
710 /* put openEntity back in list of free instances */
711 openEntity->next = parser->m_freeInternalEntities;
712 parser->m_freeInternalEntities = openEntity;
713
714- // If there are more open entities we want to stop right here and have the
715- // upcoming call to XML_ResumeParser continue with entity content, or it would
716- // be ignored altogether.
717- if (parser->m_openInternalEntities != NULL
718- && parser->m_parsingStatus.parsing == XML_SUSPENDED) {
719- return XML_ERROR_NONE;
720- }
721-
722- if (entity->is_param) {
723- int tok;
724- parser->m_processor = prologProcessor;
725- tok = XmlPrologTok(parser->m_encoding, s, end, &next);
726- return doProlog(parser, parser->m_encoding, s, end, tok, next, nextPtr,
727- (XML_Bool)! parser->m_parsingStatus.finalBuffer, XML_TRUE,
728- XML_ACCOUNT_DIRECT);
729- } else {
730- parser->m_processor = contentProcessor;
731- /* see externalEntityContentProcessor vs contentProcessor */
732- result = doContent(parser, parser->m_parentParser ? 1 : 0,
733- parser->m_encoding, s, end, nextPtr,
734- (XML_Bool)! parser->m_parsingStatus.finalBuffer,
735- XML_ACCOUNT_DIRECT);
736- if (result == XML_ERROR_NONE) {
737- if (! storeRawNames(parser))
738- return XML_ERROR_NO_MEMORY;
739- }
740- return result;
741+ if (parser->m_openInternalEntities == NULL) {
742+ parser->m_processor = entity->is_param ? prologProcessor : contentProcessor;
743 }
744+ triggerReenter(parser);
745+ return XML_ERROR_NONE;
746 }
747
748 static enum XML_Error PTRCALL
749@@ -5982,8 +6121,70 @@ static enum XML_Error
750 storeAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata,
751 const char *ptr, const char *end, STRING_POOL *pool,
752 enum XML_Account account) {
753- enum XML_Error result
754- = appendAttributeValue(parser, enc, isCdata, ptr, end, pool, account);
755+ const char *next = ptr;
756+ enum XML_Error result = XML_ERROR_NONE;
757+
758+ while (1) {
759+ if (! parser->m_openAttributeEntities) {
760+ result = appendAttributeValue(parser, enc, isCdata, next, end, pool,
761+ account, &next);
762+ } else {
763+ OPEN_INTERNAL_ENTITY *const openEntity = parser->m_openAttributeEntities;
764+ if (! openEntity)
765+ return XML_ERROR_UNEXPECTED_STATE;
766+
767+ ENTITY *const entity = openEntity->entity;
768+ const char *const textStart
769+ = ((const char *)entity->textPtr) + entity->processed;
770+ const char *const textEnd
771+ = (const char *)(entity->textPtr + entity->textLen);
772+ /* Set a safe default value in case 'next' does not get set */
773+ const char *nextInEntity = textStart;
774+ if (entity->hasMore) {
775+ result = appendAttributeValue(
776+ parser, parser->m_internalEncoding, isCdata, textStart, textEnd,
777+ pool, XML_ACCOUNT_ENTITY_EXPANSION, &nextInEntity);
778+ if (result != XML_ERROR_NONE)
779+ break;
780+ // Check if entity is complete, if not, mark down how much of it is
781+ // processed. A XML_SUSPENDED check here is not required as
782+ // appendAttributeValue will never suspend the parser.
783+ if (textEnd != nextInEntity) {
784+ entity->processed
785+ = (int)(nextInEntity - (const char *)entity->textPtr);
786+ continue;
787+ }
788+
789+ // Entity is complete. We cannot close it here since we need to first
790+ // process its possible inner entities (which are added to the
791+ // m_openAttributeEntities during appendAttributeValue)
792+ entity->hasMore = XML_FALSE;
793+ continue;
794+ } // End of entity processing, "if" block skips the rest
795+
796+ // Remove fully processed openEntity from open entity list.
797+#if XML_GE == 1
798+ entityTrackingOnClose(parser, entity, __LINE__);
799+#endif
800+ // openEntity is m_openAttributeEntities' head, since we set it at the
801+ // start of this function and because we skipped appendAttributeValue call
802+ // with hasMore set to false. This means we can directly remove the head
803+ // of m_openAttributeEntities
804+ assert(parser->m_openAttributeEntities == openEntity);
805+ entity->open = XML_FALSE;
806+ parser->m_openAttributeEntities = parser->m_openAttributeEntities->next;
807+
808+ /* put openEntity back in list of free instances */
809+ openEntity->next = parser->m_freeAttributeEntities;
810+ parser->m_freeAttributeEntities = openEntity;
811+ }
812+
813+ // Break if an error occurred or there is nothing left to process
814+ if (result || (parser->m_openAttributeEntities == NULL && end == next)) {
815+ break;
816+ }
817+ }
818+
819 if (result)
820 return result;
821 if (! isCdata && poolLength(pool) && poolLastChar(pool) == 0x20)
822@@ -5996,7 +6197,7 @@ storeAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata,
823 static enum XML_Error
824 appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata,
825 const char *ptr, const char *end, STRING_POOL *pool,
826- enum XML_Account account) {
827+ enum XML_Account account, const char **nextPtr) {
828 DTD *const dtd = parser->m_dtd; /* save one level of indirection */
829 #ifndef XML_DTD
830 UNUSED_P(account);
831@@ -6014,6 +6215,9 @@ appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata,
832 #endif
833 switch (tok) {
834 case XML_TOK_NONE:
835+ if (nextPtr) {
836+ *nextPtr = next;
837+ }
838 return XML_ERROR_NONE;
839 case XML_TOK_INVALID:
840 if (enc == parser->m_encoding)
841@@ -6154,21 +6358,11 @@ appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata,
842 return XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF;
843 } else {
844 enum XML_Error result;
845- const XML_Char *textEnd = entity->textPtr + entity->textLen;
846- entity->open = XML_TRUE;
847-#if XML_GE == 1
848- entityTrackingOnOpen(parser, entity, __LINE__);
849-#endif
850- result = appendAttributeValue(parser, parser->m_internalEncoding,
851- isCdata, (const char *)entity->textPtr,
852- (const char *)textEnd, pool,
853- XML_ACCOUNT_ENTITY_EXPANSION);
854-#if XML_GE == 1
855- entityTrackingOnClose(parser, entity, __LINE__);
856-#endif
857- entity->open = XML_FALSE;
858- if (result)
859- return result;
860+ result = processEntity(parser, entity, XML_FALSE, ENTITY_ATTRIBUTE);
861+ if ((result == XML_ERROR_NONE) && (nextPtr != NULL)) {
862+ *nextPtr = next;
863+ }
864+ return result;
865 }
866 } break;
867 default:
868@@ -6197,7 +6391,7 @@ appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata,
869 static enum XML_Error
870 storeEntityValue(XML_Parser parser, const ENCODING *enc,
871 const char *entityTextPtr, const char *entityTextEnd,
872- enum XML_Account account) {
873+ enum XML_Account account, const char **nextPtr) {
874 DTD *const dtd = parser->m_dtd; /* save one level of indirection */
875 STRING_POOL *pool = &(dtd->entityValuePool);
876 enum XML_Error result = XML_ERROR_NONE;
877@@ -6215,8 +6409,9 @@ storeEntityValue(XML_Parser parser, const ENCODING *enc,
878 return XML_ERROR_NO_MEMORY;
879 }
880
881+ const char *next;
882 for (;;) {
883- const char *next
884+ next
885 = entityTextPtr; /* XmlEntityValueTok doesn't always set the last arg */
886 int tok = XmlEntityValueTok(enc, entityTextPtr, entityTextEnd, &next);
887
888@@ -6278,16 +6473,8 @@ storeEntityValue(XML_Parser parser, const ENCODING *enc,
889 } else
890 dtd->keepProcessing = dtd->standalone;
891 } else {
892- entity->open = XML_TRUE;
893- entityTrackingOnOpen(parser, entity, __LINE__);
894- result = storeEntityValue(
895- parser, parser->m_internalEncoding, (const char *)entity->textPtr,
896- (const char *)(entity->textPtr + entity->textLen),
897- XML_ACCOUNT_ENTITY_EXPANSION);
898- entityTrackingOnClose(parser, entity, __LINE__);
899- entity->open = XML_FALSE;
900- if (result)
901- goto endEntityValue;
902+ result = processEntity(parser, entity, XML_FALSE, ENTITY_VALUE);
903+ goto endEntityValue;
904 }
905 break;
906 }
907@@ -6375,6 +6562,81 @@ endEntityValue:
908 # ifdef XML_DTD
909 parser->m_prologState.inEntityValue = oldInEntityValue;
910 # endif /* XML_DTD */
911+ // If 'nextPtr' is given, it should be updated during the processing
912+ if (nextPtr != NULL) {
913+ *nextPtr = next;
914+ }
915+ return result;
916+}
917+
918+static enum XML_Error
919+callStoreEntityValue(XML_Parser parser, const ENCODING *enc,
920+ const char *entityTextPtr, const char *entityTextEnd,
921+ enum XML_Account account) {
922+ const char *next = entityTextPtr;
923+ enum XML_Error result = XML_ERROR_NONE;
924+ while (1) {
925+ if (! parser->m_openValueEntities) {
926+ result
927+ = storeEntityValue(parser, enc, next, entityTextEnd, account, &next);
928+ } else {
929+ OPEN_INTERNAL_ENTITY *const openEntity = parser->m_openValueEntities;
930+ if (! openEntity)
931+ return XML_ERROR_UNEXPECTED_STATE;
932+
933+ ENTITY *const entity = openEntity->entity;
934+ const char *const textStart
935+ = ((const char *)entity->textPtr) + entity->processed;
936+ const char *const textEnd
937+ = (const char *)(entity->textPtr + entity->textLen);
938+ /* Set a safe default value in case 'next' does not get set */
939+ const char *nextInEntity = textStart;
940+ if (entity->hasMore) {
941+ result = storeEntityValue(parser, parser->m_internalEncoding, textStart,
942+ textEnd, XML_ACCOUNT_ENTITY_EXPANSION,
943+ &nextInEntity);
944+ if (result != XML_ERROR_NONE)
945+ break;
946+ // Check if entity is complete, if not, mark down how much of it is
947+ // processed. A XML_SUSPENDED check here is not required as
948+ // appendAttributeValue will never suspend the parser.
949+ if (textEnd != nextInEntity) {
950+ entity->processed
951+ = (int)(nextInEntity - (const char *)entity->textPtr);
952+ continue;
953+ }
954+
955+ // Entity is complete. We cannot close it here since we need to first
956+ // process its possible inner entities (which are added to the
957+ // m_openValueEntities during storeEntityValue)
958+ entity->hasMore = XML_FALSE;
959+ continue;
960+ } // End of entity processing, "if" block skips the rest
961+
962+ // Remove fully processed openEntity from open entity list.
963+# if XML_GE == 1
964+ entityTrackingOnClose(parser, entity, __LINE__);
965+# endif
966+ // openEntity is m_openValueEntities' head, since we set it at the
967+ // start of this function and because we skipped storeEntityValue call
968+ // with hasMore set to false. This means we can directly remove the head
969+ // of m_openValueEntities
970+ assert(parser->m_openValueEntities == openEntity);
971+ entity->open = XML_FALSE;
972+ parser->m_openValueEntities = parser->m_openValueEntities->next;
973+
974+ /* put openEntity back in list of free instances */
975+ openEntity->next = parser->m_freeValueEntities;
976+ parser->m_freeValueEntities = openEntity;
977+ }
978+
979+ // Break if an error occurred or there is nothing left to process
980+ if (result
981+ || (parser->m_openValueEntities == NULL && entityTextEnd == next)) {
982+ break;
983+ }
984+ }
985+
986 return result;
987 }
988
989diff --git a/expat/tests/alloc_tests.c b/expat/tests/alloc_tests.c
990index e5d46ebe..12ea3b2a 100644
991--- a/expat/tests/alloc_tests.c
992+++ b/expat/tests/alloc_tests.c
993@@ -19,6 +19,7 @@
994 Copyright (c) 2020 Tim Gates <tim.gates@iress.com>
995 Copyright (c) 2021 Donghee Na <donghee.na@python.org>
996 Copyright (c) 2023 Sony Corporation / Snild Dolkow <snild@sony.com>
997+ Copyright (c) 2025 Berkay Eren Ürün <berkay.ueruen@siemens.com>
998 Licensed under the MIT license:
999
1000 Permission is hereby granted, free of charge, to any person obtaining
1001@@ -450,6 +451,31 @@ START_TEST(test_alloc_internal_entity) {
1002 }
1003 END_TEST
1004
1005+START_TEST(test_alloc_parameter_entity) {
1006+ const char *text = "<!DOCTYPE foo ["
1007+ "<!ENTITY % param1 \"<!ENTITY internal 'some_text'>\">"
1008+ "%param1;"
1009+ "]> <foo>&internal;content</foo>";
1010+ int i;
1011+ const int alloc_test_max_repeats = 30;
1012+
1013+ for (i = 0; i < alloc_test_max_repeats; i++) {
1014+ g_allocation_count = i;
1015+ XML_SetParamEntityParsing(g_parser, XML_PARAM_ENTITY_PARSING_ALWAYS);
1016+ if (_XML_Parse_SINGLE_BYTES(g_parser, text, (int)strlen(text), XML_TRUE)
1017+ != XML_STATUS_ERROR)
1018+ break;
1019+ alloc_teardown();
1020+ alloc_setup();
1021+ }
1022+ g_allocation_count = -1;
1023+ if (i == 0)
1024+ fail("Parameter entity processed despite duff allocator");
1025+ if (i == alloc_test_max_repeats)
1026+ fail("Parameter entity not processed at max allocation count");
1027+}
1028+END_TEST
1029+
1030 /* Test the robustness against allocation failure of element handling
1031 * Based on test_dtd_default_handling().
1032 */
1033@@ -2079,6 +2105,7 @@ make_alloc_test_case(Suite *s) {
1034 tcase_add_test__ifdef_xml_dtd(tc_alloc, test_alloc_external_entity);
1035 tcase_add_test__ifdef_xml_dtd(tc_alloc, test_alloc_ext_entity_set_encoding);
1036 tcase_add_test__ifdef_xml_dtd(tc_alloc, test_alloc_internal_entity);
1037+ tcase_add_test__ifdef_xml_dtd(tc_alloc, test_alloc_parameter_entity);
1038 tcase_add_test__ifdef_xml_dtd(tc_alloc, test_alloc_dtd_default_handling);
1039 tcase_add_test(tc_alloc, test_alloc_explicit_encoding);
1040 tcase_add_test(tc_alloc, test_alloc_set_base);
1041diff --git a/expat/tests/basic_tests.c b/expat/tests/basic_tests.c
1042index d2306772..29be32cf 100644
1043--- a/expat/tests/basic_tests.c
1044+++ b/expat/tests/basic_tests.c
1045@@ -10,7 +10,7 @@
1046 Copyright (c) 2003 Greg Stein <gstein@users.sourceforge.net>
1047 Copyright (c) 2005-2007 Steven Solie <steven@solie.ca>
1048 Copyright (c) 2005-2012 Karl Waclawek <karl@waclawek.net>
1049- Copyright (c) 2016-2024 Sebastian Pipping <sebastian@pipping.org>
1050+ Copyright (c) 2016-2025 Sebastian Pipping <sebastian@pipping.org>
1051 Copyright (c) 2017-2022 Rhodri James <rhodri@wildebeest.org.uk>
1052 Copyright (c) 2017 Joe Orton <jorton@redhat.com>
1053 Copyright (c) 2017 José Gutiérrez de la Concha <jose@zeroc.com>
1054@@ -19,6 +19,7 @@
1055 Copyright (c) 2020 Tim Gates <tim.gates@iress.com>
1056 Copyright (c) 2021 Donghee Na <donghee.na@python.org>
1057 Copyright (c) 2023-2024 Sony Corporation / Snild Dolkow <snild@sony.com>
1058+ Copyright (c) 2024-2025 Berkay Eren Ürün <berkay.ueruen@siemens.com>
1059 Licensed under the MIT license:
1060
1061 Permission is hereby granted, free of charge, to any person obtaining
1062@@ -1233,44 +1234,58 @@ START_TEST(test_no_indirectly_recursive_entity_refs) {
1063 "<doc/>\n",
1064 true},
1065 };
1066+ const XML_Bool reset_or_not[] = {XML_TRUE, XML_FALSE};
1067+
1068 for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
1069- const char *const doc = cases[i].doc;
1070- const bool usesParameterEntities = cases[i].usesParameterEntities;
1071+ for (size_t j = 0; j < sizeof(reset_or_not) / sizeof(reset_or_not[0]);
1072+ j++) {
1073+ const XML_Bool reset_wanted = reset_or_not[j];
1074+ const char *const doc = cases[i].doc;
1075+ const bool usesParameterEntities = cases[i].usesParameterEntities;
1076
1077- set_subtest("[%i] %s", (int)i, doc);
1078+ set_subtest("[%i,reset=%i] %s", (int)i, (int)j, doc);
1079
1080 #ifdef XML_DTD // both GE and DTD
1081- const bool rejection_expected = true;
1082+ const bool rejection_expected = true;
1083 #elif XML_GE == 1 // GE but not DTD
1084- const bool rejection_expected = ! usesParameterEntities;
1085+ const bool rejection_expected = ! usesParameterEntities;
1086 #else // neither DTD nor GE
1087- const bool rejection_expected = false;
1088+ const bool rejection_expected = false;
1089 #endif
1090
1091- XML_Parser parser = XML_ParserCreate(NULL);
1092+ XML_Parser parser = XML_ParserCreate(NULL);
1093
1094 #ifdef XML_DTD
1095- if (usesParameterEntities) {
1096- assert_true(
1097- XML_SetParamEntityParsing(parser, XML_PARAM_ENTITY_PARSING_ALWAYS)
1098- == 1);
1099- }
1100+ if (usesParameterEntities) {
1101+ assert_true(
1102+ XML_SetParamEntityParsing(parser, XML_PARAM_ENTITY_PARSING_ALWAYS)
1103+ == 1);
1104+ }
1105 #else
1106- UNUSED_P(usesParameterEntities);
1107+ UNUSED_P(usesParameterEntities);
1108 #endif // XML_DTD
1109
1110- const enum XML_Status status
1111- = _XML_Parse_SINGLE_BYTES(parser, doc, (int)strlen(doc),
1112- /*isFinal*/ XML_TRUE);
1113+ const enum XML_Status status
1114+ = _XML_Parse_SINGLE_BYTES(parser, doc, (int)strlen(doc),
1115+ /*isFinal*/ XML_TRUE);
1116
1117- if (rejection_expected) {
1118- assert_true(status == XML_STATUS_ERROR);
1119- assert_true(XML_GetErrorCode(parser) == XML_ERROR_RECURSIVE_ENTITY_REF);
1120- } else {
1121- assert_true(status == XML_STATUS_OK);
1122+ if (rejection_expected) {
1123+ assert_true(status == XML_STATUS_ERROR);
1124+ assert_true(XML_GetErrorCode(parser) == XML_ERROR_RECURSIVE_ENTITY_REF);
1125+ } else {
1126+ assert_true(status == XML_STATUS_OK);
1127+ }
1128+
1129+ if (reset_wanted) {
1130+ // This covers free'ing of (eventually) all three open entity lists by
1131+ // XML_ParserReset.
1132+ XML_ParserReset(parser, NULL);
1133+ }
1134+
1135+ // This covers free'ing of (eventually) all three open entity lists by
1136+ // XML_ParserFree (unless XML_ParserReset has already done that above).
1137+ XML_ParserFree(parser);
1138 }
1139-
1140- XML_ParserFree(parser);
1141 }
1142 }
1143 END_TEST
1144@@ -4033,7 +4048,7 @@ START_TEST(test_skipped_null_loaded_ext_entity) {
1145 = {"<!ENTITY % pe1 SYSTEM 'http://example.org/two.ent'>\n"
1146 "<!ENTITY % pe2 '%pe1;'>\n"
1147 "%pe2;\n",
1148- external_entity_null_loader};
1149+ external_entity_null_loader, NULL};
1150
1151 XML_SetUserData(g_parser, &test_data);
1152 XML_SetParamEntityParsing(g_parser, XML_PARAM_ENTITY_PARSING_ALWAYS);
1153@@ -4051,7 +4066,7 @@ START_TEST(test_skipped_unloaded_ext_entity) {
1154 = {"<!ENTITY % pe1 SYSTEM 'http://example.org/two.ent'>\n"
1155 "<!ENTITY % pe2 '%pe1;'>\n"
1156 "%pe2;\n",
1157- NULL};
1158+ NULL, NULL};
1159
1160 XML_SetUserData(g_parser, &test_data);
1161 XML_SetParamEntityParsing(g_parser, XML_PARAM_ENTITY_PARSING_ALWAYS);
1162@@ -5351,6 +5366,151 @@ START_TEST(test_pool_integrity_with_unfinished_attr) {
1163 }
1164 END_TEST
1165
1166+/* Test a possible early return location in internalEntityProcessor */
1167+START_TEST(test_entity_ref_no_elements) {
1168+ const char *const text = "<!DOCTYPE foo [\n"
1169+ "<!ENTITY e1 \"test\">\n"
1170+ "]> <foo>&e1;"; // intentionally missing newline
1171+
1172+ XML_Parser parser = XML_ParserCreate(NULL);
1173+ assert_true(_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE)
1174+ == XML_STATUS_ERROR);
1175+ assert_true(XML_GetErrorCode(parser) == XML_ERROR_NO_ELEMENTS);
1176+ XML_ParserFree(parser);
1177+}
1178+END_TEST
1179+
1180+/* Tests if chained entity references lead to unbounded recursion */
1181+START_TEST(test_deep_nested_entity) {
1182+ const size_t N_LINES = 60000;
1183+ const size_t SIZE_PER_LINE = 50;
1184+
1185+ char *const text = (char *)malloc((N_LINES + 4) * SIZE_PER_LINE);
1186+ if (text == NULL) {
1187+ fail("malloc failed");
1188+ }
1189+
1190+ char *textPtr = text;
1191+
1192+ // Create the XML
1193+ textPtr += snprintf(textPtr, SIZE_PER_LINE,
1194+ "<!DOCTYPE foo [\n"
1195+ " <!ENTITY s0 'deepText'>\n");
1196+
1197+ for (size_t i = 1; i < N_LINES; ++i) {
1198+ textPtr += snprintf(textPtr, SIZE_PER_LINE, " <!ENTITY s%lu '&s%lu;'>\n",
1199+ (long unsigned)i, (long unsigned)(i - 1));
1200+ }
1201+
1202+ snprintf(textPtr, SIZE_PER_LINE, "]> <foo>&s%lu;</foo>\n",
1203+ (long unsigned)(N_LINES - 1));
1204+
1205+ const XML_Char *const expected = XCS("deepText");
1206+
1207+ CharData storage;
1208+ CharData_Init(&storage);
1209+
1210+ XML_Parser parser = XML_ParserCreate(NULL);
1211+
1212+ XML_SetCharacterDataHandler(parser, accumulate_characters);
1213+ XML_SetUserData(parser, &storage);
1214+
1215+ if (_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE)
1216+ == XML_STATUS_ERROR)
1217+ xml_failure(parser);
1218+
1219+ CharData_CheckXMLChars(&storage, expected);
1220+ XML_ParserFree(parser);
1221+ free(text);
1222+}
1223+END_TEST
1224+
1225+/* Tests if chained entity references in attributes
1226+lead to unbounded recursion */
1227+START_TEST(test_deep_nested_attribute_entity) {
1228+ const size_t N_LINES = 60000;
1229+ const size_t SIZE_PER_LINE = 100;
1230+
1231+ char *const text = (char *)malloc((N_LINES + 4) * SIZE_PER_LINE);
1232+ if (text == NULL) {
1233+ fail("malloc failed");
1234+ }
1235+
1236+ char *textPtr = text;
1237+
1238+ // Create the XML
1239+ textPtr += snprintf(textPtr, SIZE_PER_LINE,
1240+ "<!DOCTYPE foo [\n"
1241+ " <!ENTITY s0 'deepText'>\n");
1242+
1243+ for (size_t i = 1; i < N_LINES; ++i) {
1244+ textPtr += snprintf(textPtr, SIZE_PER_LINE, " <!ENTITY s%lu '&s%lu;'>\n",
1245+ (long unsigned)i, (long unsigned)(i - 1));
1246+ }
1247+
1248+ snprintf(textPtr, SIZE_PER_LINE, "]> <foo name='&s%lu;'>mainText</foo>\n",
1249+ (long unsigned)(N_LINES - 1));
1250+
1251+ AttrInfo doc_info[] = {{XCS("name"), XCS("deepText")}, {NULL, NULL}};
1252+ ElementInfo info[] = {{XCS("foo"), 1, NULL, NULL}, {NULL, 0, NULL, NULL}};
1253+ info[0].attributes = doc_info;
1254+
1255+ XML_Parser parser = XML_ParserCreate(NULL);
1256+ ParserAndElementInfo parserPlusElemenInfo = {parser, info};
1257+
1258+ XML_SetStartElementHandler(parser, counting_start_element_handler);
1259+ XML_SetUserData(parser, &parserPlusElemenInfo);
1260+
1261+ if (_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE)
1262+ == XML_STATUS_ERROR)
1263+ xml_failure(parser);
1264+
1265+ XML_ParserFree(parser);
1266+ free(text);
1267+}
1268+END_TEST
1269+
1270+START_TEST(test_deep_nested_entity_delayed_interpretation) {
1271+ const size_t N_LINES = 70000;
1272+ const size_t SIZE_PER_LINE = 100;
1273+
1274+ char *const text = (char *)malloc((N_LINES + 4) * SIZE_PER_LINE);
1275+ if (text == NULL) {
1276+ fail("malloc failed");
1277+ }
1278+
1279+ char *textPtr = text;
1280+
1281+ // Create the XML
1282+ textPtr += snprintf(textPtr, SIZE_PER_LINE,
1283+ "<!DOCTYPE foo [\n"
1284+ " <!ENTITY %% s0 'deepText'>\n");
1285+
1286+ for (size_t i = 1; i < N_LINES; ++i) {
1287+ textPtr += snprintf(textPtr, SIZE_PER_LINE,
1288+ " <!ENTITY %% s%lu '&#37;s%lu;'>\n", (long unsigned)i,
1289+ (long unsigned)(i - 1));
1290+ }
1291+
1292+ snprintf(textPtr, SIZE_PER_LINE,
1293+ " <!ENTITY %% define_g \"<!ENTITY g '&#37;s%lu;'>\">\n"
1294+ " %%define_g;\n"
1295+ "]>\n"
1296+ "<foo/>\n",
1297+ (long unsigned)(N_LINES - 1));
1298+
1299+ XML_Parser parser = XML_ParserCreate(NULL);
1300+
1301+ XML_SetParamEntityParsing(parser, XML_PARAM_ENTITY_PARSING_ALWAYS);
1302+ if (_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE)
1303+ == XML_STATUS_ERROR)
1304+ xml_failure(parser);
1305+
1306+ XML_ParserFree(parser);
1307+ free(text);
1308+}
1309+END_TEST
1310+
1311 START_TEST(test_nested_entity_suspend) {
1312 const char *const text = "<!DOCTYPE a [\n"
1313 " <!ENTITY e1 '<!--e1-->'>\n"
1314@@ -5381,6 +5541,35 @@ START_TEST(test_nested_entity_suspend) {
1315 }
1316 END_TEST
1317
1318+START_TEST(test_nested_entity_suspend_2) {
1319+ const char *const text = "<!DOCTYPE doc [\n"
1320+ " <!ENTITY ge1 'head1Ztail1'>\n"
1321+ " <!ENTITY ge2 'head2&ge1;tail2'>\n"
1322+ " <!ENTITY ge3 'head3&ge2;tail3'>\n"
1323+ "]>\n"
1324+ "<doc>&ge3;</doc>";
1325+ const XML_Char *const expected = XCS("head3") XCS("head2") XCS("head1")
1326+ XCS("Z") XCS("tail1") XCS("tail2") XCS("tail3");
1327+ CharData storage;
1328+ CharData_Init(&storage);
1329+ XML_Parser parser = XML_ParserCreate(NULL);
1330+ ParserPlusStorage parserPlusStorage = {parser, &storage};
1331+
1332+ XML_SetCharacterDataHandler(parser, accumulate_char_data_and_suspend);
1333+ XML_SetUserData(parser, &parserPlusStorage);
1334+
1335+ enum XML_Status status = XML_Parse(parser, text, (int)strlen(text), XML_TRUE);
1336+ while (status == XML_STATUS_SUSPENDED) {
1337+ status = XML_ResumeParser(parser);
1338+ }
1339+ if (status != XML_STATUS_OK)
1340+ xml_failure(parser);
1341+
1342+ CharData_CheckXMLChars(&storage, expected);
1343+ XML_ParserFree(parser);
1344+}
1345+END_TEST
1346+
1347 /* Regression test for quadratic parsing on large tokens */
1348 START_TEST(test_big_tokens_scale_linearly) {
1349 const struct {
1350@@ -6221,7 +6410,13 @@ make_basic_test_case(Suite *s) {
1351 tcase_add_test(tc_basic, test_empty_element_abort);
1352 tcase_add_test__ifdef_xml_dtd(tc_basic,
1353 test_pool_integrity_with_unfinished_attr);
1354+ tcase_add_test__if_xml_ge(tc_basic, test_entity_ref_no_elements);
1355+ tcase_add_test__if_xml_ge(tc_basic, test_deep_nested_entity);
1356+ tcase_add_test__if_xml_ge(tc_basic, test_deep_nested_attribute_entity);
1357+ tcase_add_test__if_xml_ge(tc_basic,
1358+ test_deep_nested_entity_delayed_interpretation);
1359 tcase_add_test__if_xml_ge(tc_basic, test_nested_entity_suspend);
1360+ tcase_add_test__if_xml_ge(tc_basic, test_nested_entity_suspend_2);
1361 tcase_add_test(tc_basic, test_big_tokens_scale_linearly);
1362 tcase_add_test(tc_basic, test_set_reparse_deferral);
1363 tcase_add_test(tc_basic, test_reparse_deferral_is_inherited);
1364diff --git a/expat/tests/handlers.c b/expat/tests/handlers.c
1365index 0211985f..f15029e3 100644
1366--- a/expat/tests/handlers.c
1367+++ b/expat/tests/handlers.c
1368@@ -1882,6 +1882,20 @@ accumulate_entity_decl(void *userData, const XML_Char *entityName,
1369 CharData_AppendXMLChars(storage, XCS("\n"), 1);
1370 }
1371
1372+void XMLCALL
1373+accumulate_char_data_and_suspend(void *userData, const XML_Char *s, int len) {
1374+ ParserPlusStorage *const parserPlusStorage = (ParserPlusStorage *)userData;
1375+
1376+ CharData_AppendXMLChars(parserPlusStorage->storage, s, len);
1377+
1378+ for (int i = 0; i < len; i++) {
1379+ if (s[i] == 'Z') {
1380+ XML_StopParser(parserPlusStorage->parser, /*resumable=*/XML_TRUE);
1381+ break;
1382+ }
1383+ }
1384+}
1385+
1386 void XMLCALL
1387 accumulate_start_element(void *userData, const XML_Char *name,
1388 const XML_Char **atts) {
1389diff --git a/expat/tests/handlers.h b/expat/tests/handlers.h
1390index 8850bb94..4d6a08d5 100644
1391--- a/expat/tests/handlers.h
1392+++ b/expat/tests/handlers.h
1393@@ -325,6 +325,7 @@ extern int XMLCALL external_entity_devaluer(XML_Parser parser,
1394 typedef struct ext_hdlr_data {
1395 const char *parse_text;
1396 XML_ExternalEntityRefHandler handler;
1397+ CharData *storage;
1398 } ExtHdlrData;
1399
1400 extern int XMLCALL external_entity_oneshot_loader(XML_Parser parser,
1401@@ -569,6 +570,10 @@ extern void XMLCALL accumulate_entity_decl(
1402 const XML_Char *systemId, const XML_Char *publicId,
1403 const XML_Char *notationName);
1404
1405+extern void XMLCALL accumulate_char_data_and_suspend(void *userData,
1406+ const XML_Char *s,
1407+ int len);
1408+
1409 extern void XMLCALL accumulate_start_element(void *userData,
1410 const XML_Char *name,
1411 const XML_Char **atts);
1412diff --git a/expat/tests/misc_tests.c b/expat/tests/misc_tests.c
1413index 9afe0922..f9a78f66 100644
1414--- a/expat/tests/misc_tests.c
1415+++ b/expat/tests/misc_tests.c
1416@@ -59,6 +59,9 @@
1417 #include "handlers.h"
1418 #include "misc_tests.h"
1419
1420+void XMLCALL accumulate_characters_ext_handler(void *userData,
1421+ const XML_Char *s, int len);
1422+
1423 /* Test that a failure to allocate the parser structure fails gracefully */
1424 START_TEST(test_misc_alloc_create_parser) {
1425 XML_Memory_Handling_Suite memsuite = {duff_allocator, realloc, free};
1426@@ -519,6 +522,45 @@ START_TEST(test_misc_stopparser_rejects_unstarted_parser) {
1427 }
1428 END_TEST
1429
1430+/* Adaptation of accumulate_characters that takes ExtHdlrData input to work with
1431+ * test_renter_loop_finite_content below */
1432+void XMLCALL
1433+accumulate_characters_ext_handler(void *userData, const XML_Char *s, int len) {
1434+ ExtHdlrData *const test_data = (ExtHdlrData *)userData;
1435+ CharData_AppendXMLChars(test_data->storage, s, len);
1436+}
1437+
1438+/* Test that internalEntityProcessor does not re-enter forever;
1439+ * based on files tests/xmlconf/xmltest/valid/ext-sa/012.{xml,ent} */
1440+START_TEST(test_renter_loop_finite_content) {
1441+ CharData storage;
1442+ CharData_Init(&storage);
1443+ const char *const text = "<!DOCTYPE doc [\n"
1444+ "<!ENTITY e1 '&e2;'>\n"
1445+ "<!ENTITY e2 '&e3;'>\n"
1446+ "<!ENTITY e3 SYSTEM '012.ent'>\n"
1447+ "<!ENTITY e4 '&e5;'>\n"
1448+ "<!ENTITY e5 '(e5)'>\n"
1449+ "<!ELEMENT doc (#PCDATA)>\n"
1450+ "]>\n"
1451+ "<doc>&e1;</doc>\n";
1452+ ExtHdlrData test_data = {"&e4;\n", external_entity_null_loader, &storage};
1453+ const XML_Char *const expected = XCS("(e5)\n");
1454+
1455+ XML_Parser parser = XML_ParserCreate(NULL);
1456+ assert_true(parser != NULL);
1457+ XML_SetUserData(parser, &test_data);
1458+ XML_SetExternalEntityRefHandler(parser, external_entity_oneshot_loader);
1459+ XML_SetCharacterDataHandler(parser, accumulate_characters_ext_handler);
1460+ if (_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE)
1461+ == XML_STATUS_ERROR)
1462+ xml_failure(parser);
1463+
1464+ CharData_CheckXMLChars(&storage, expected);
1465+ XML_ParserFree(parser);
1466+}
1467+END_TEST
1468+
1469 void
1470 make_miscellaneous_test_case(Suite *s) {
1471 TCase *tc_misc = tcase_create("miscellaneous tests");
1472@@ -545,4 +587,5 @@ make_miscellaneous_test_case(Suite *s) {
1473 tcase_add_test(tc_misc, test_misc_char_handler_stop_without_leak);
1474 tcase_add_test(tc_misc, test_misc_resumeparser_not_crashing);
1475 tcase_add_test(tc_misc, test_misc_stopparser_rejects_unstarted_parser);
1476+ tcase_add_test__if_xml_ge(tc_misc, test_renter_loop_finite_content);
1477 }
diff --git a/meta/recipes-core/expat/expat/CVE-2024-8176-02.patch b/meta/recipes-core/expat/expat/CVE-2024-8176-02.patch
new file mode 100644
index 0000000000..a22ace3be6
--- /dev/null
+++ b/meta/recipes-core/expat/expat/CVE-2024-8176-02.patch
@@ -0,0 +1,248 @@
1From 5f7af592557495a99e7badaf5c03362a20650156 Mon Sep 17 00:00:00 2001
2From: Peter Marko <peter.marko@siemens.com>
3Date: Thu, 27 Mar 2025 20:28:26 +0100
4Subject: [PATCH] Stop updating event pointer on exit for reentry (fixes #980)
5 #989
6
7Fixes #980
8
9CVE: CVE-2024-8176
10Upstream-Status: Backport [https://github.com/libexpat/libexpat/pull/989]
11Signed-off-by: Peter Marko <peter.marko@siemens.com>
12---
13 expat/Changes | 15 ++++++++++++
14 expat/lib/xmlparse.c | 12 ++++++---
15 expat/tests/common.c | 25 +++++++++++++++++++
16 expat/tests/common.h | 2 ++
17 expat/tests/misc_tests.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++
18 5 files changed, 112 insertions(+), 3 deletions(-)
19
20diff --git a/expat/Changes b/expat/Changes
21index 8c5db88c..7ba33497 100644
22--- a/expat/Changes
23+++ b/expat/Changes
24@@ -30,6 +30,21 @@
25 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
26
27 Patches:
28+ Bug fixes:
29+ #980 #989 Restore event pointer behavior from Expat 2.6.4
30+ (that the fix to CVE-2024-8176 changed in 2.7.0);
31+ affected API functions are:
32+ - XML_GetCurrentByteCount
33+ - XML_GetCurrentByteIndex
34+ - XML_GetCurrentColumnNumber
35+ - XML_GetCurrentLineNumber
36+ - XML_GetInputContext
37+
38+ Special thanks to:
39+ Berkay Eren Ürün
40+ and
41+ Perl XML::Parser
42+
43 Security fixes:
44 #893 #??? CVE-2024-8176 -- Fix crash from chaining a large number
45 of entities caused by stack overflow by resolving use of
46diff --git a/expat/lib/xmlparse.c b/expat/lib/xmlparse.c
47index 473c791d..c6085d38 100644
48--- a/expat/lib/xmlparse.c
49+++ b/expat/lib/xmlparse.c
50@@ -3402,12 +3402,13 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc,
51 break;
52 /* LCOV_EXCL_STOP */
53 }
54- *eventPP = s = next;
55 switch (parser->m_parsingStatus.parsing) {
56 case XML_SUSPENDED:
57+ *eventPP = next;
58 *nextPtr = next;
59 return XML_ERROR_NONE;
60 case XML_FINISHED:
61+ *eventPP = next;
62 return XML_ERROR_ABORTED;
63 case XML_PARSING:
64 if (parser->m_reenter) {
65@@ -3416,6 +3417,7 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc,
66 }
67 /* Fall through */
68 default:;
69+ *eventPP = s = next;
70 }
71 }
72 /* not reached */
73@@ -4332,12 +4334,13 @@ doCdataSection(XML_Parser parser, const ENCODING *enc, const char **startPtr,
74 /* LCOV_EXCL_STOP */
75 }
76
77- *eventPP = s = next;
78 switch (parser->m_parsingStatus.parsing) {
79 case XML_SUSPENDED:
80+ *eventPP = next;
81 *nextPtr = next;
82 return XML_ERROR_NONE;
83 case XML_FINISHED:
84+ *eventPP = next;
85 return XML_ERROR_ABORTED;
86 case XML_PARSING:
87 if (parser->m_reenter) {
88@@ -4345,6 +4348,7 @@ doCdataSection(XML_Parser parser, const ENCODING *enc, const char **startPtr,
89 }
90 /* Fall through */
91 default:;
92+ *eventPP = s = next;
93 }
94 }
95 /* not reached */
96@@ -5951,12 +5955,13 @@ epilogProcessor(XML_Parser parser, const char *s, const char *end,
97 default:
98 return XML_ERROR_JUNK_AFTER_DOC_ELEMENT;
99 }
100- parser->m_eventPtr = s = next;
101 switch (parser->m_parsingStatus.parsing) {
102 case XML_SUSPENDED:
103+ parser->m_eventPtr = next;
104 *nextPtr = next;
105 return XML_ERROR_NONE;
106 case XML_FINISHED:
107+ parser->m_eventPtr = next;
108 return XML_ERROR_ABORTED;
109 case XML_PARSING:
110 if (parser->m_reenter) {
111@@ -5964,6 +5969,7 @@ epilogProcessor(XML_Parser parser, const char *s, const char *end,
112 }
113 /* Fall through */
114 default:;
115+ parser->m_eventPtr = s = next;
116 }
117 }
118 }
119diff --git a/expat/tests/common.c b/expat/tests/common.c
120index 3aea8d74..b267dbb3 100644
121--- a/expat/tests/common.c
122+++ b/expat/tests/common.c
123@@ -42,6 +42,8 @@
124 */
125
126 #include <assert.h>
127+#include <errno.h>
128+#include <stdint.h> // for SIZE_MAX
129 #include <stdio.h>
130 #include <string.h>
131
132@@ -294,3 +296,26 @@ duff_reallocator(void *ptr, size_t size) {
133 g_reallocation_count--;
134 return realloc(ptr, size);
135 }
136+
137+// Portable remake of strndup(3) for C99; does not care about space efficiency
138+char *
139+portable_strndup(const char *s, size_t n) {
140+ if ((s == NULL) || (n == SIZE_MAX)) {
141+ errno = EINVAL;
142+ return NULL;
143+ }
144+
145+ char *const buffer = (char *)malloc(n + 1);
146+ if (buffer == NULL) {
147+ errno = ENOMEM;
148+ return NULL;
149+ }
150+
151+ errno = 0;
152+
153+ memcpy(buffer, s, n);
154+
155+ buffer[n] = '\0';
156+
157+ return buffer;
158+}
159diff --git a/expat/tests/common.h b/expat/tests/common.h
160index bc4c7da6..88711308 100644
161--- a/expat/tests/common.h
162+++ b/expat/tests/common.h
163@@ -146,6 +146,8 @@ extern void *duff_allocator(size_t size);
164
165 extern void *duff_reallocator(void *ptr, size_t size);
166
167+extern char *portable_strndup(const char *s, size_t n);
168+
169 #endif /* XML_COMMON_H */
170
171 #ifdef __cplusplus
172diff --git a/expat/tests/misc_tests.c b/expat/tests/misc_tests.c
173index f9a78f66..2b9f793b 100644
174--- a/expat/tests/misc_tests.c
175+++ b/expat/tests/misc_tests.c
176@@ -561,6 +561,66 @@ START_TEST(test_renter_loop_finite_content) {
177 }
178 END_TEST
179
180+// Inspired by function XML_OriginalString of Perl's XML::Parser
181+static char *
182+dup_original_string(XML_Parser parser) {
183+ const int byte_count = XML_GetCurrentByteCount(parser);
184+
185+ assert_true(byte_count >= 0);
186+
187+ int offset = -1;
188+ int size = -1;
189+
190+ const char *const context = XML_GetInputContext(parser, &offset, &size);
191+
192+#if XML_CONTEXT_BYTES > 0
193+ assert_true(context != NULL);
194+ assert_true(offset >= 0);
195+ assert_true(size >= 0);
196+ return portable_strndup(context + offset, byte_count);
197+#else
198+ assert_true(context == NULL);
199+ return NULL;
200+#endif
201+}
202+
203+static void
204+on_characters_issue_980(void *userData, const XML_Char *s, int len) {
205+ (void)s;
206+ (void)len;
207+ XML_Parser parser = (XML_Parser)userData;
208+
209+ char *const original_string = dup_original_string(parser);
210+
211+#if XML_CONTEXT_BYTES > 0
212+ assert_true(original_string != NULL);
213+ assert_true(strcmp(original_string, "&draft.day;") == 0);
214+ free(original_string);
215+#else
216+ assert_true(original_string == NULL);
217+#endif
218+}
219+
220+START_TEST(test_misc_expected_event_ptr_issue_980) {
221+ // NOTE: This is a tiny subset of sample "REC-xml-19980210.xml"
222+ // from Perl's XML::Parser
223+ const char *const doc = "<!DOCTYPE day [\n"
224+ " <!ENTITY draft.day '10'>\n"
225+ "]>\n"
226+ "<day>&draft.day;</day>\n";
227+
228+ XML_Parser parser = XML_ParserCreate(NULL);
229+ XML_SetUserData(parser, parser);
230+ XML_SetCharacterDataHandler(parser, on_characters_issue_980);
231+
232+ assert_true(_XML_Parse_SINGLE_BYTES(parser, doc, (int)strlen(doc),
233+ /*isFinal=*/XML_TRUE)
234+ == XML_STATUS_OK);
235+
236+ XML_ParserFree(parser);
237+}
238+END_TEST
239+
240 void
241 make_miscellaneous_test_case(Suite *s) {
242 TCase *tc_misc = tcase_create("miscellaneous tests");
243@@ -588,4 +648,5 @@ make_miscellaneous_test_case(Suite *s) {
244 tcase_add_test(tc_misc, test_misc_resumeparser_not_crashing);
245 tcase_add_test(tc_misc, test_misc_stopparser_rejects_unstarted_parser);
246 tcase_add_test__if_xml_ge(tc_misc, test_renter_loop_finite_content);
247+ tcase_add_test(tc_misc, test_misc_expected_event_ptr_issue_980);
248 }
diff --git a/meta/recipes-core/expat/expat_2.6.4.bb b/meta/recipes-core/expat/expat_2.6.4.bb
index f383792793..ab0b1d54c1 100644
--- a/meta/recipes-core/expat/expat_2.6.4.bb
+++ b/meta/recipes-core/expat/expat_2.6.4.bb
@@ -10,6 +10,9 @@ VERSION_TAG = "${@d.getVar('PV').replace('.', '_')}"
10 10
11SRC_URI = "${GITHUB_BASE_URI}/download/R_${VERSION_TAG}/expat-${PV}.tar.bz2 \ 11SRC_URI = "${GITHUB_BASE_URI}/download/R_${VERSION_TAG}/expat-${PV}.tar.bz2 \
12 file://run-ptest \ 12 file://run-ptest \
13 file://0001-tests-Cover-indirect-entity-recursion.patch;striplevel=2 \
14 file://CVE-2024-8176-01.patch;striplevel=2 \
15 file://CVE-2024-8176-02.patch;striplevel=2 \
13 " 16 "
14 17
15GITHUB_BASE_URI = "https://github.com/libexpat/libexpat/releases/" 18GITHUB_BASE_URI = "https://github.com/libexpat/libexpat/releases/"