diff options
author | Peter Marko <peter.marko@siemens.com> | 2025-03-27 22:44:09 +0100 |
---|---|---|
committer | Steve Sakoman <steve@sakoman.com> | 2025-04-07 06:34:44 -0700 |
commit | 5ceb4646d23078dba1395f6512ba81277394cd32 (patch) | |
tree | 7db7837792510b2cb3c0da80b1bc87a2fd245cef | |
parent | 2af52d4819aea9ca746f0671e2b2f8b5c7de2960 (diff) | |
download | poky-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>
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 @@ | |||
1 | From 3d5fdbb44e80ed789e4f6510542d77d6284fbd0e Mon Sep 17 00:00:00 2001 | ||
2 | From: Sebastian Pipping <sebastian@pipping.org> | ||
3 | Date: Sat, 23 Nov 2024 14:20:21 +0100 | ||
4 | Subject: [PATCH] tests: Cover indirect entity recursion | ||
5 | |||
6 | Upstream-Status: Backport [https://github.com/libexpat/libexpat/commit/3d5fdbb44e80ed789e4f6510542d77d6284fbd0e] | ||
7 | Signed-off-by: Peter Marko <peter.marko@siemens.com> | ||
8 | --- | ||
9 | expat/tests/basic_tests.c | 74 +++++++++++++++++++++++++++++++++++++++ | ||
10 | 1 file changed, 74 insertions(+) | ||
11 | |||
12 | diff --git a/expat/tests/basic_tests.c b/expat/tests/basic_tests.c | ||
13 | index 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 '%p2;'>\n" | ||
44 | + " <!ENTITY % p2 '%p1;'>\n" | ||
45 | + " <!ENTITY % define_g \"<!ENTITY g '%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 @@ | |||
1 | From 3f924a715cfa97e70df1c24334d2d728973d1020 Mon Sep 17 00:00:00 2001 | ||
2 | From: Peter Marko <peter.marko@siemens.com> | ||
3 | Date: Mon, 17 Mar 2025 20:41:24 +0100 | ||
4 | Subject: [PATCH] [CVE-2024-8176] Resolve the recursion during entity | ||
5 | processing to prevent stack overflow (fixes #893) | ||
6 | |||
7 | Fixes #893 | ||
8 | |||
9 | CVE: CVE-2024-8176 | ||
10 | Upstream-Status: Backport [https://github.com/libexpat/libexpat/pull/973] | ||
11 | Signed-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 | |||
22 | diff --git a/expat/Changes b/expat/Changes | ||
23 | index 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 | ||
69 | diff --git a/expat/lib/xmlparse.c b/expat/lib/xmlparse.c | ||
70 | index 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 | |||
989 | diff --git a/expat/tests/alloc_tests.c b/expat/tests/alloc_tests.c | ||
990 | index 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); | ||
1041 | diff --git a/expat/tests/basic_tests.c b/expat/tests/basic_tests.c | ||
1042 | index 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 '%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 '%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); | ||
1364 | diff --git a/expat/tests/handlers.c b/expat/tests/handlers.c | ||
1365 | index 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) { | ||
1389 | diff --git a/expat/tests/handlers.h b/expat/tests/handlers.h | ||
1390 | index 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); | ||
1412 | diff --git a/expat/tests/misc_tests.c b/expat/tests/misc_tests.c | ||
1413 | index 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 @@ | |||
1 | From 5f7af592557495a99e7badaf5c03362a20650156 Mon Sep 17 00:00:00 2001 | ||
2 | From: Peter Marko <peter.marko@siemens.com> | ||
3 | Date: Thu, 27 Mar 2025 20:28:26 +0100 | ||
4 | Subject: [PATCH] Stop updating event pointer on exit for reentry (fixes #980) | ||
5 | #989 | ||
6 | |||
7 | Fixes #980 | ||
8 | |||
9 | CVE: CVE-2024-8176 | ||
10 | Upstream-Status: Backport [https://github.com/libexpat/libexpat/pull/989] | ||
11 | Signed-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 | |||
20 | diff --git a/expat/Changes b/expat/Changes | ||
21 | index 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 | ||
46 | diff --git a/expat/lib/xmlparse.c b/expat/lib/xmlparse.c | ||
47 | index 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 | } | ||
119 | diff --git a/expat/tests/common.c b/expat/tests/common.c | ||
120 | index 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 | +} | ||
159 | diff --git a/expat/tests/common.h b/expat/tests/common.h | ||
160 | index 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 | ||
172 | diff --git a/expat/tests/misc_tests.c b/expat/tests/misc_tests.c | ||
173 | index 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 | ||
11 | SRC_URI = "${GITHUB_BASE_URI}/download/R_${VERSION_TAG}/expat-${PV}.tar.bz2 \ | 11 | SRC_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 | ||
15 | GITHUB_BASE_URI = "https://github.com/libexpat/libexpat/releases/" | 18 | GITHUB_BASE_URI = "https://github.com/libexpat/libexpat/releases/" |