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/" |
