diff --git a/SPECS/expat/CVE-2024-8176.patch b/SPECS/expat/CVE-2024-8176.patch deleted file mode 100644 index 47f38ef6dd7..00000000000 --- a/SPECS/expat/CVE-2024-8176.patch +++ /dev/null @@ -1,1398 +0,0 @@ -From 35a1dea4c07cb0f13f0736d3a8821a696c27f8b1 Mon Sep 17 00:00:00 2001 -From: Kshitiz Godara -Date: Thu, 20 Mar 2025 06:36:42 +0000 -Subject: [PATCH] Fix for CVE-2024-8176 - -Upstream source: https://github.com/libexpat/libexpat/pull/973 - -Signed-off-by: Kshitiz Godara ---- - Changes | 30 ++- - lib/xmlparse.c | 566 ++++++++++++++++++++++++++++++++------------ - tests/alloc_tests.c | 27 +++ - tests/basic_tests.c | 187 ++++++++++++++- - tests/handlers.c | 15 ++ - tests/handlers.h | 5 + - tests/misc_tests.c | 43 ++++ - 7 files changed, 717 insertions(+), 156 deletions(-) - -diff --git a/Changes b/Changes -index aa19f70..75c62d6 100644 ---- a/Changes -+++ b/Changes -@@ -11,7 +11,6 @@ - !! The following topics need *additional skilled C developers* to progress !! - !! in a timely manner or at all (loosely ordered by descending priority): !! - !! !! --!! - fixing a complex non-public security issue, !! - !! - teaming up on researching and fixing future security reports and !! - !! ClusterFuzz findings with few-days-max response times in communication !! - !! in order to (1) have a sound fix ready before the end of a 90 days !! -@@ -30,6 +29,35 @@ - !! THANK YOU! Sebastian Pipping -- Berlin, 2024-03-09 !! - !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - -+ Security fixes: -+ #893 #??? CVE-2024-8176 -- Fix crash from chaining a large number -+ of entities caused by stack overflow by resolving use of -+ recursion, for all three uses of entities: -+ - general entities in character data ("&g1;") -+ - general entities in attribute values ("") -+ - parameter entities ("%p1;") -+ Known impact is (reliable and easy) denial of service: -+ 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 -+ (Base Score: 7.5, Temporal Score: 7.2) -+ Please note that a layer of compression around XML can -+ significantly reduce the minimum attack payload size. -+ -+ Special thanks to: -+ Alexander Gieringer -+ Berkay Eren Ürün -+ Jann Horn -+ Mark Brand -+ Sebastian Andrzej Siewior -+ Snild Dolkow -+ Thomas Pröll -+ Tomas Korbar -+ valord577 -+ and -+ Google Project Zero -+ Linutronix -+ Red Hat -+ Siemens -+ - Release 2.6.4 Wed November 6 2024 - Security fixes: - #915 CVE-2024-50602 -- Fix crash within function XML_ResumeParser -diff --git a/lib/xmlparse.c b/lib/xmlparse.c -index a4e091e..473c791 100644 ---- a/lib/xmlparse.c -+++ b/lib/xmlparse.c -@@ -39,7 +39,7 @@ - Copyright (c) 2022 Sean McBride - Copyright (c) 2023 Owain Davies - Copyright (c) 2023-2024 Sony Corporation / Snild Dolkow -- Copyright (c) 2024 Berkay Eren Ürün -+ Copyright (c) 2024-2025 Berkay Eren Ürün - Copyright (c) 2024 Hanno Böck - Licensed under the MIT license: - -@@ -325,6 +325,10 @@ typedef struct { - const XML_Char *publicId; - const XML_Char *notation; - XML_Bool open; -+ XML_Bool hasMore; /* true if entity has not been completely processed */ -+ /* An entity can be open while being already completely processed (hasMore == -+ XML_FALSE). The reason is the delayed closing of entities until their inner -+ entities are processed and closed */ - XML_Bool is_param; - XML_Bool is_internal; /* true if declared in internal subset outside PE */ - } ENTITY; -@@ -415,6 +419,12 @@ typedef struct { - int *scaffIndex; - } DTD; - -+enum EntityType { -+ ENTITY_INTERNAL, -+ ENTITY_ATTRIBUTE, -+ ENTITY_VALUE, -+}; -+ - typedef struct open_internal_entity { - const char *internalEventPtr; - const char *internalEventEndPtr; -@@ -422,6 +432,7 @@ typedef struct open_internal_entity { - ENTITY *entity; - int startTagLevel; - XML_Bool betweenDecl; /* WFC: PE Between Declarations */ -+ enum EntityType type; - } OPEN_INTERNAL_ENTITY; - - enum XML_Account { -@@ -481,8 +492,8 @@ static enum XML_Error doProlog(XML_Parser parser, const ENCODING *enc, - const char *next, const char **nextPtr, - XML_Bool haveMore, XML_Bool allowClosingDoctype, - enum XML_Account account); --static enum XML_Error processInternalEntity(XML_Parser parser, ENTITY *entity, -- XML_Bool betweenDecl); -+static enum XML_Error processEntity(XML_Parser parser, ENTITY *entity, -+ XML_Bool betweenDecl, enum EntityType type); - static enum XML_Error doContent(XML_Parser parser, int startTagLevel, - const ENCODING *enc, const char *start, - const char *end, const char **endPtr, -@@ -513,18 +524,22 @@ static enum XML_Error storeAttributeValue(XML_Parser parser, - const char *ptr, const char *end, - STRING_POOL *pool, - enum XML_Account account); --static enum XML_Error appendAttributeValue(XML_Parser parser, -- const ENCODING *enc, -- XML_Bool isCdata, const char *ptr, -- const char *end, STRING_POOL *pool, -- enum XML_Account account); -+static enum XML_Error -+appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata, -+ const char *ptr, const char *end, STRING_POOL *pool, -+ enum XML_Account account, const char **nextPtr); - static ATTRIBUTE_ID *getAttributeId(XML_Parser parser, const ENCODING *enc, - const char *start, const char *end); - static int setElementTypePrefix(XML_Parser parser, ELEMENT_TYPE *elementType); - #if XML_GE == 1 - static enum XML_Error storeEntityValue(XML_Parser parser, const ENCODING *enc, - const char *start, const char *end, -- enum XML_Account account); -+ enum XML_Account account, -+ const char **nextPtr); -+static enum XML_Error callStoreEntityValue(XML_Parser parser, -+ const ENCODING *enc, -+ const char *start, const char *end, -+ enum XML_Account account); - #else - static enum XML_Error storeSelfEntityValue(XML_Parser parser, ENTITY *entity); - #endif -@@ -709,6 +724,10 @@ struct XML_ParserStruct { - const char *m_positionPtr; - OPEN_INTERNAL_ENTITY *m_openInternalEntities; - OPEN_INTERNAL_ENTITY *m_freeInternalEntities; -+ OPEN_INTERNAL_ENTITY *m_openAttributeEntities; -+ OPEN_INTERNAL_ENTITY *m_freeAttributeEntities; -+ OPEN_INTERNAL_ENTITY *m_openValueEntities; -+ OPEN_INTERNAL_ENTITY *m_freeValueEntities; - XML_Bool m_defaultExpandInternalEntities; - int m_tagLevel; - ENTITY *m_declEntity; -@@ -756,6 +775,7 @@ struct XML_ParserStruct { - ACCOUNTING m_accounting; - ENTITY_STATS m_entity_stats; - #endif -+ XML_Bool m_reenter; - }; - - #define MALLOC(parser, s) (parser->m_mem.malloc_fcn((s))) -@@ -1028,7 +1048,29 @@ callProcessor(XML_Parser parser, const char *start, const char *end, - #if defined(XML_TESTING) - g_bytesScanned += (unsigned)have_now; - #endif -- const enum XML_Error ret = parser->m_processor(parser, start, end, endPtr); -+ // Run in a loop to eliminate dangerous recursion depths -+ enum XML_Error ret; -+ *endPtr = start; -+ while (1) { -+ // Use endPtr as the new start in each iteration, since it will -+ // be set to the next start point by m_processor. -+ ret = parser->m_processor(parser, *endPtr, end, endPtr); -+ -+ // Make parsing status (and in particular XML_SUSPENDED) take -+ // precedence over re-enter flag when they disagree -+ if (parser->m_parsingStatus.parsing != XML_PARSING) { -+ parser->m_reenter = XML_FALSE; -+ } -+ -+ if (! parser->m_reenter) { -+ break; -+ } -+ -+ parser->m_reenter = XML_FALSE; -+ if (ret != XML_ERROR_NONE) -+ return ret; -+ } -+ - if (ret == XML_ERROR_NONE) { - // if we consumed nothing, remember what we had on this parse attempt. - if (*endPtr == start) { -@@ -1139,6 +1181,8 @@ parserCreate(const XML_Char *encodingName, - parser->m_freeBindingList = NULL; - parser->m_freeTagList = NULL; - parser->m_freeInternalEntities = NULL; -+ parser->m_freeAttributeEntities = NULL; -+ parser->m_freeValueEntities = NULL; - - parser->m_groupSize = 0; - parser->m_groupConnector = NULL; -@@ -1241,6 +1285,8 @@ parserInit(XML_Parser parser, const XML_Char *encodingName) { - parser->m_eventEndPtr = NULL; - parser->m_positionPtr = NULL; - parser->m_openInternalEntities = NULL; -+ parser->m_openAttributeEntities = NULL; -+ parser->m_openValueEntities = NULL; - parser->m_defaultExpandInternalEntities = XML_TRUE; - parser->m_tagLevel = 0; - parser->m_tagStack = NULL; -@@ -1251,6 +1297,8 @@ parserInit(XML_Parser parser, const XML_Char *encodingName) { - parser->m_unknownEncodingData = NULL; - parser->m_parentParser = NULL; - parser->m_parsingStatus.parsing = XML_INITIALIZED; -+ // Reentry can only be triggered inside m_processor calls -+ parser->m_reenter = XML_FALSE; - #ifdef XML_DTD - parser->m_isParamEntity = XML_FALSE; - parser->m_useForeignDTD = XML_FALSE; -@@ -1310,6 +1358,24 @@ XML_ParserReset(XML_Parser parser, const XML_Char *encodingName) { - openEntity->next = parser->m_freeInternalEntities; - parser->m_freeInternalEntities = openEntity; - } -+ /* move m_openAttributeEntities to m_freeAttributeEntities (i.e. same task but -+ * for attributes) */ -+ openEntityList = parser->m_openAttributeEntities; -+ while (openEntityList) { -+ OPEN_INTERNAL_ENTITY *openEntity = openEntityList; -+ openEntityList = openEntity->next; -+ openEntity->next = parser->m_freeAttributeEntities; -+ parser->m_freeAttributeEntities = openEntity; -+ } -+ /* move m_openValueEntities to m_freeValueEntities (i.e. same task but -+ * for value entities) */ -+ openEntityList = parser->m_openValueEntities; -+ while (openEntityList) { -+ OPEN_INTERNAL_ENTITY *openEntity = openEntityList; -+ openEntityList = openEntity->next; -+ openEntity->next = parser->m_freeValueEntities; -+ parser->m_freeValueEntities = openEntity; -+ } - moveToFreeBindingList(parser, parser->m_inheritedBindings); - FREE(parser, parser->m_unknownEncodingMem); - if (parser->m_unknownEncodingRelease) -@@ -1323,6 +1389,19 @@ XML_ParserReset(XML_Parser parser, const XML_Char *encodingName) { - return XML_TRUE; - } - -+static XML_Bool -+parserBusy(XML_Parser parser) { -+ switch (parser->m_parsingStatus.parsing) { -+ case XML_PARSING: -+ case XML_SUSPENDED: -+ return XML_TRUE; -+ case XML_INITIALIZED: -+ case XML_FINISHED: -+ default: -+ return XML_FALSE; -+ } -+} -+ - enum XML_Status XMLCALL - XML_SetEncoding(XML_Parser parser, const XML_Char *encodingName) { - if (parser == NULL) -@@ -1331,8 +1410,7 @@ XML_SetEncoding(XML_Parser parser, const XML_Char *encodingName) { - XXX There's no way for the caller to determine which of the - XXX possible error cases caused the XML_STATUS_ERROR return. - */ -- if (parser->m_parsingStatus.parsing == XML_PARSING -- || parser->m_parsingStatus.parsing == XML_SUSPENDED) -+ if (parserBusy(parser)) - return XML_STATUS_ERROR; - - /* Get rid of any previous encoding name */ -@@ -1569,7 +1647,34 @@ XML_ParserFree(XML_Parser parser) { - entityList = entityList->next; - FREE(parser, openEntity); - } -- -+ /* free m_openAttributeEntities and m_freeAttributeEntities */ -+ entityList = parser->m_openAttributeEntities; -+ for (;;) { -+ OPEN_INTERNAL_ENTITY *openEntity; -+ if (entityList == NULL) { -+ if (parser->m_freeAttributeEntities == NULL) -+ break; -+ entityList = parser->m_freeAttributeEntities; -+ parser->m_freeAttributeEntities = NULL; -+ } -+ openEntity = entityList; -+ entityList = entityList->next; -+ FREE(parser, openEntity); -+ } -+ /* free m_openValueEntities and m_freeValueEntities */ -+ entityList = parser->m_openValueEntities; -+ for (;;) { -+ OPEN_INTERNAL_ENTITY *openEntity; -+ if (entityList == NULL) { -+ if (parser->m_freeValueEntities == NULL) -+ break; -+ entityList = parser->m_freeValueEntities; -+ parser->m_freeValueEntities = NULL; -+ } -+ openEntity = entityList; -+ entityList = entityList->next; -+ FREE(parser, openEntity); -+ } - destroyBindings(parser->m_freeBindingList, parser); - destroyBindings(parser->m_inheritedBindings, parser); - poolDestroy(&parser->m_tempPool); -@@ -1611,8 +1716,7 @@ XML_UseForeignDTD(XML_Parser parser, XML_Bool useDTD) { - return XML_ERROR_INVALID_ARGUMENT; - #ifdef XML_DTD - /* block after XML_Parse()/XML_ParseBuffer() has been called */ -- if (parser->m_parsingStatus.parsing == XML_PARSING -- || parser->m_parsingStatus.parsing == XML_SUSPENDED) -+ if (parserBusy(parser)) - return XML_ERROR_CANT_CHANGE_FEATURE_ONCE_PARSING; - parser->m_useForeignDTD = useDTD; - return XML_ERROR_NONE; -@@ -1627,8 +1731,7 @@ XML_SetReturnNSTriplet(XML_Parser parser, int do_nst) { - if (parser == NULL) - return; - /* block after XML_Parse()/XML_ParseBuffer() has been called */ -- if (parser->m_parsingStatus.parsing == XML_PARSING -- || parser->m_parsingStatus.parsing == XML_SUSPENDED) -+ if (parserBusy(parser)) - return; - parser->m_ns_triplets = do_nst ? XML_TRUE : XML_FALSE; - } -@@ -1897,8 +2000,7 @@ XML_SetParamEntityParsing(XML_Parser parser, - if (parser == NULL) - return 0; - /* block after XML_Parse()/XML_ParseBuffer() has been called */ -- if (parser->m_parsingStatus.parsing == XML_PARSING -- || parser->m_parsingStatus.parsing == XML_SUSPENDED) -+ if (parserBusy(parser)) - return 0; - #ifdef XML_DTD - parser->m_paramEntityParsing = peParsing; -@@ -1915,8 +2017,7 @@ XML_SetHashSalt(XML_Parser parser, unsigned long hash_salt) { - if (parser->m_parentParser) - return XML_SetHashSalt(parser->m_parentParser, hash_salt); - /* block after XML_Parse()/XML_ParseBuffer() has been called */ -- if (parser->m_parsingStatus.parsing == XML_PARSING -- || parser->m_parsingStatus.parsing == XML_SUSPENDED) -+ if (parserBusy(parser)) - return 0; - parser->m_hash_secret_salt = hash_salt; - return 1; -@@ -2230,6 +2331,11 @@ XML_GetBuffer(XML_Parser parser, int len) { - return parser->m_bufferEnd; - } - -+static void -+triggerReenter(XML_Parser parser) { -+ parser->m_reenter = XML_TRUE; -+} -+ - enum XML_Status XMLCALL - XML_StopParser(XML_Parser parser, XML_Bool resumable) { - if (parser == NULL) -@@ -2704,8 +2810,9 @@ static enum XML_Error PTRCALL - contentProcessor(XML_Parser parser, const char *start, const char *end, - const char **endPtr) { - enum XML_Error result = doContent( -- parser, 0, parser->m_encoding, start, end, endPtr, -- (XML_Bool)! parser->m_parsingStatus.finalBuffer, XML_ACCOUNT_DIRECT); -+ parser, parser->m_parentParser ? 1 : 0, parser->m_encoding, start, end, -+ endPtr, (XML_Bool)! parser->m_parsingStatus.finalBuffer, -+ XML_ACCOUNT_DIRECT); - if (result == XML_ERROR_NONE) { - if (! storeRawNames(parser)) - return XML_ERROR_NO_MEMORY; -@@ -2793,6 +2900,11 @@ externalEntityInitProcessor3(XML_Parser parser, const char *start, - return XML_ERROR_NONE; - case XML_FINISHED: - return XML_ERROR_ABORTED; -+ case XML_PARSING: -+ if (parser->m_reenter) { -+ return XML_ERROR_UNEXPECTED_STATE; // LCOV_EXCL_LINE -+ } -+ /* Fall through */ - default: - start = next; - } -@@ -2966,7 +3078,7 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc, - reportDefault(parser, enc, s, next); - break; - } -- result = processInternalEntity(parser, entity, XML_FALSE); -+ result = processEntity(parser, entity, XML_FALSE, ENTITY_INTERNAL); - if (result != XML_ERROR_NONE) - return result; - } else if (parser->m_externalEntityRefHandler) { -@@ -3092,7 +3204,9 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc, - } - if ((parser->m_tagLevel == 0) - && (parser->m_parsingStatus.parsing != XML_FINISHED)) { -- if (parser->m_parsingStatus.parsing == XML_SUSPENDED) -+ if (parser->m_parsingStatus.parsing == XML_SUSPENDED -+ || (parser->m_parsingStatus.parsing == XML_PARSING -+ && parser->m_reenter)) - parser->m_processor = epilogProcessor; - else - return epilogProcessor(parser, next, end, nextPtr); -@@ -3153,7 +3267,9 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc, - } - if ((parser->m_tagLevel == 0) - && (parser->m_parsingStatus.parsing != XML_FINISHED)) { -- if (parser->m_parsingStatus.parsing == XML_SUSPENDED) -+ if (parser->m_parsingStatus.parsing == XML_SUSPENDED -+ || (parser->m_parsingStatus.parsing == XML_PARSING -+ && parser->m_reenter)) - parser->m_processor = epilogProcessor; - else - return epilogProcessor(parser, next, end, nextPtr); -@@ -3293,6 +3409,12 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc, - return XML_ERROR_NONE; - case XML_FINISHED: - return XML_ERROR_ABORTED; -+ case XML_PARSING: -+ if (parser->m_reenter) { -+ *nextPtr = next; -+ return XML_ERROR_NONE; -+ } -+ /* Fall through */ - default:; - } - } -@@ -4217,6 +4339,11 @@ doCdataSection(XML_Parser parser, const ENCODING *enc, const char **startPtr, - return XML_ERROR_NONE; - case XML_FINISHED: - return XML_ERROR_ABORTED; -+ case XML_PARSING: -+ if (parser->m_reenter) { -+ return XML_ERROR_UNEXPECTED_STATE; // LCOV_EXCL_LINE -+ } -+ /* Fall through */ - default:; - } - } -@@ -4549,7 +4676,7 @@ entityValueInitProcessor(XML_Parser parser, const char *s, const char *end, - } - /* found end of entity value - can store it now */ - return storeEntityValue(parser, parser->m_encoding, s, end, -- XML_ACCOUNT_DIRECT); -+ XML_ACCOUNT_DIRECT, NULL); - } else if (tok == XML_TOK_XML_DECL) { - enum XML_Error result; - result = processXmlDecl(parser, 0, start, next); -@@ -4676,7 +4803,7 @@ entityValueProcessor(XML_Parser parser, const char *s, const char *end, - break; - } - /* found end of entity value - can store it now */ -- return storeEntityValue(parser, enc, s, end, XML_ACCOUNT_DIRECT); -+ return storeEntityValue(parser, enc, s, end, XML_ACCOUNT_DIRECT, NULL); - } - start = next; - } -@@ -5119,9 +5246,9 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, - #if XML_GE == 1 - // This will store the given replacement text in - // parser->m_declEntity->textPtr. -- enum XML_Error result -- = storeEntityValue(parser, enc, s + enc->minBytesPerChar, -- next - enc->minBytesPerChar, XML_ACCOUNT_NONE); -+ enum XML_Error result = callStoreEntityValue( -+ parser, enc, s + enc->minBytesPerChar, next - enc->minBytesPerChar, -+ XML_ACCOUNT_NONE); - if (parser->m_declEntity) { - parser->m_declEntity->textPtr = poolStart(&dtd->entityValuePool); - parser->m_declEntity->textLen -@@ -5546,7 +5673,7 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, - enum XML_Error result; - XML_Bool betweenDecl - = (role == XML_ROLE_PARAM_ENTITY_REF ? XML_TRUE : XML_FALSE); -- result = processInternalEntity(parser, entity, betweenDecl); -+ result = processEntity(parser, entity, betweenDecl, ENTITY_INTERNAL); - if (result != XML_ERROR_NONE) - return result; - handleDefault = XML_FALSE; -@@ -5751,6 +5878,12 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, - return XML_ERROR_NONE; - case XML_FINISHED: - return XML_ERROR_ABORTED; -+ case XML_PARSING: -+ if (parser->m_reenter) { -+ *nextPtr = next; -+ return XML_ERROR_NONE; -+ } -+ /* Fall through */ - default: - s = next; - tok = XmlPrologTok(enc, s, end, &next); -@@ -5825,21 +5958,49 @@ epilogProcessor(XML_Parser parser, const char *s, const char *end, - return XML_ERROR_NONE; - case XML_FINISHED: - return XML_ERROR_ABORTED; -+ case XML_PARSING: -+ if (parser->m_reenter) { -+ return XML_ERROR_UNEXPECTED_STATE; // LCOV_EXCL_LINE -+ } -+ /* Fall through */ - default:; - } - } - } - - static enum XML_Error --processInternalEntity(XML_Parser parser, ENTITY *entity, XML_Bool betweenDecl) { -- const char *textStart, *textEnd; -- const char *next; -- enum XML_Error result; -- OPEN_INTERNAL_ENTITY *openEntity; -+processEntity(XML_Parser parser, ENTITY *entity, XML_Bool betweenDecl, -+ enum EntityType type) { -+ OPEN_INTERNAL_ENTITY *openEntity, **openEntityList, **freeEntityList; -+ switch (type) { -+ case ENTITY_INTERNAL: -+ parser->m_processor = internalEntityProcessor; -+ openEntityList = &parser->m_openInternalEntities; -+ freeEntityList = &parser->m_freeInternalEntities; -+ break; -+ case ENTITY_ATTRIBUTE: -+ openEntityList = &parser->m_openAttributeEntities; -+ freeEntityList = &parser->m_freeAttributeEntities; -+ break; -+ case ENTITY_VALUE: -+ openEntityList = &parser->m_openValueEntities; -+ freeEntityList = &parser->m_freeValueEntities; -+ break; -+ /* default case serves merely as a safety net in case of a -+ * wrong entityType. Therefore we exclude the following lines -+ * from the test coverage. -+ * -+ * LCOV_EXCL_START -+ */ -+ default: -+ // Should not reach here -+ assert(0); -+ /* LCOV_EXCL_STOP */ -+ } - -- if (parser->m_freeInternalEntities) { -- openEntity = parser->m_freeInternalEntities; -- parser->m_freeInternalEntities = openEntity->next; -+ if (*freeEntityList) { -+ openEntity = *freeEntityList; -+ *freeEntityList = openEntity->next; - } else { - openEntity - = (OPEN_INTERNAL_ENTITY *)MALLOC(parser, sizeof(OPEN_INTERNAL_ENTITY)); -@@ -5847,55 +6008,34 @@ processInternalEntity(XML_Parser parser, ENTITY *entity, XML_Bool betweenDecl) { - return XML_ERROR_NO_MEMORY; - } - entity->open = XML_TRUE; -+ entity->hasMore = XML_TRUE; - #if XML_GE == 1 - entityTrackingOnOpen(parser, entity, __LINE__); - #endif - entity->processed = 0; -- openEntity->next = parser->m_openInternalEntities; -- parser->m_openInternalEntities = openEntity; -+ openEntity->next = *openEntityList; -+ *openEntityList = openEntity; - openEntity->entity = entity; -+ openEntity->type = type; - openEntity->startTagLevel = parser->m_tagLevel; - openEntity->betweenDecl = betweenDecl; - openEntity->internalEventPtr = NULL; - openEntity->internalEventEndPtr = NULL; -- textStart = (const char *)entity->textPtr; -- textEnd = (const char *)(entity->textPtr + entity->textLen); -- /* Set a safe default value in case 'next' does not get set */ -- next = textStart; -- -- if (entity->is_param) { -- int tok -- = XmlPrologTok(parser->m_internalEncoding, textStart, textEnd, &next); -- result = doProlog(parser, parser->m_internalEncoding, textStart, textEnd, -- tok, next, &next, XML_FALSE, XML_FALSE, -- XML_ACCOUNT_ENTITY_EXPANSION); -- } else { -- result = doContent(parser, parser->m_tagLevel, parser->m_internalEncoding, -- textStart, textEnd, &next, XML_FALSE, -- XML_ACCOUNT_ENTITY_EXPANSION); -- } - -- if (result == XML_ERROR_NONE) { -- if (textEnd != next && parser->m_parsingStatus.parsing == XML_SUSPENDED) { -- entity->processed = (int)(next - textStart); -- parser->m_processor = internalEntityProcessor; -- } else if (parser->m_openInternalEntities->entity == entity) { --#if XML_GE == 1 -- entityTrackingOnClose(parser, entity, __LINE__); --#endif /* XML_GE == 1 */ -- entity->open = XML_FALSE; -- parser->m_openInternalEntities = openEntity->next; -- /* put openEntity back in list of free instances */ -- openEntity->next = parser->m_freeInternalEntities; -- parser->m_freeInternalEntities = openEntity; -- } -+ // Only internal entities make use of the reenter flag -+ // therefore no need to set it for other entity types -+ if (type == ENTITY_INTERNAL) { -+ triggerReenter(parser); - } -- return result; -+ return XML_ERROR_NONE; - } - - static enum XML_Error PTRCALL - internalEntityProcessor(XML_Parser parser, const char *s, const char *end, - const char **nextPtr) { -+ UNUSED_P(s); -+ UNUSED_P(end); -+ UNUSED_P(nextPtr); - ENTITY *entity; - const char *textStart, *textEnd; - const char *next; -@@ -5905,68 +6045,67 @@ internalEntityProcessor(XML_Parser parser, const char *s, const char *end, - return XML_ERROR_UNEXPECTED_STATE; - - entity = openEntity->entity; -- textStart = ((const char *)entity->textPtr) + entity->processed; -- textEnd = (const char *)(entity->textPtr + entity->textLen); -- /* Set a safe default value in case 'next' does not get set */ -- next = textStart; -- -- if (entity->is_param) { -- int tok -- = XmlPrologTok(parser->m_internalEncoding, textStart, textEnd, &next); -- result = doProlog(parser, parser->m_internalEncoding, textStart, textEnd, -- tok, next, &next, XML_FALSE, XML_TRUE, -- XML_ACCOUNT_ENTITY_EXPANSION); -- } else { -- result = doContent(parser, openEntity->startTagLevel, -- parser->m_internalEncoding, textStart, textEnd, &next, -- XML_FALSE, XML_ACCOUNT_ENTITY_EXPANSION); -- } - -- if (result != XML_ERROR_NONE) -- return result; -+ // This will return early -+ if (entity->hasMore) { -+ textStart = ((const char *)entity->textPtr) + entity->processed; -+ textEnd = (const char *)(entity->textPtr + entity->textLen); -+ /* Set a safe default value in case 'next' does not get set */ -+ next = textStart; -+ -+ if (entity->is_param) { -+ int tok -+ = XmlPrologTok(parser->m_internalEncoding, textStart, textEnd, &next); -+ result = doProlog(parser, parser->m_internalEncoding, textStart, textEnd, -+ tok, next, &next, XML_FALSE, XML_FALSE, -+ XML_ACCOUNT_ENTITY_EXPANSION); -+ } else { -+ result = doContent(parser, openEntity->startTagLevel, -+ parser->m_internalEncoding, textStart, textEnd, &next, -+ XML_FALSE, XML_ACCOUNT_ENTITY_EXPANSION); -+ } -+ -+ if (result != XML_ERROR_NONE) -+ return result; -+ // Check if entity is complete, if not, mark down how much of it is -+ // processed -+ if (textEnd != next -+ && (parser->m_parsingStatus.parsing == XML_SUSPENDED -+ || (parser->m_parsingStatus.parsing == XML_PARSING -+ && parser->m_reenter))) { -+ entity->processed = (int)(next - (const char *)entity->textPtr); -+ return result; -+ } - -- if (textEnd != next && parser->m_parsingStatus.parsing == XML_SUSPENDED) { -- entity->processed = (int)(next - (const char *)entity->textPtr); -+ // Entity is complete. We cannot close it here since we need to first -+ // process its possible inner entities (which are added to the -+ // m_openInternalEntities during doProlog or doContent calls above) -+ entity->hasMore = XML_FALSE; -+ triggerReenter(parser); - return result; -- } -+ } // End of entity processing, "if" block will return here - -+ // Remove fully processed openEntity from open entity list. - #if XML_GE == 1 - entityTrackingOnClose(parser, entity, __LINE__); - #endif -+ // openEntity is m_openInternalEntities' head, as we set it at the start of -+ // this function and we skipped doProlog and doContent calls with hasMore set -+ // to false. This means we can directly remove the head of -+ // m_openInternalEntities -+ assert(parser->m_openInternalEntities == openEntity); - entity->open = XML_FALSE; -- parser->m_openInternalEntities = openEntity->next; -+ parser->m_openInternalEntities = parser->m_openInternalEntities->next; -+ - /* put openEntity back in list of free instances */ - openEntity->next = parser->m_freeInternalEntities; - parser->m_freeInternalEntities = openEntity; - -- // If there are more open entities we want to stop right here and have the -- // upcoming call to XML_ResumeParser continue with entity content, or it would -- // be ignored altogether. -- if (parser->m_openInternalEntities != NULL -- && parser->m_parsingStatus.parsing == XML_SUSPENDED) { -- return XML_ERROR_NONE; -- } -- -- if (entity->is_param) { -- int tok; -- parser->m_processor = prologProcessor; -- tok = XmlPrologTok(parser->m_encoding, s, end, &next); -- return doProlog(parser, parser->m_encoding, s, end, tok, next, nextPtr, -- (XML_Bool)! parser->m_parsingStatus.finalBuffer, XML_TRUE, -- XML_ACCOUNT_DIRECT); -- } else { -- parser->m_processor = contentProcessor; -- /* see externalEntityContentProcessor vs contentProcessor */ -- result = doContent(parser, parser->m_parentParser ? 1 : 0, -- parser->m_encoding, s, end, nextPtr, -- (XML_Bool)! parser->m_parsingStatus.finalBuffer, -- XML_ACCOUNT_DIRECT); -- if (result == XML_ERROR_NONE) { -- if (! storeRawNames(parser)) -- return XML_ERROR_NO_MEMORY; -- } -- return result; -+ if (parser->m_openInternalEntities == NULL) { -+ parser->m_processor = entity->is_param ? prologProcessor : contentProcessor; - } -+ triggerReenter(parser); -+ return XML_ERROR_NONE; - } - - static enum XML_Error PTRCALL -@@ -5982,8 +6121,70 @@ static enum XML_Error - storeAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata, - const char *ptr, const char *end, STRING_POOL *pool, - enum XML_Account account) { -- enum XML_Error result -- = appendAttributeValue(parser, enc, isCdata, ptr, end, pool, account); -+ const char *next = ptr; -+ enum XML_Error result = XML_ERROR_NONE; -+ -+ while (1) { -+ if (! parser->m_openAttributeEntities) { -+ result = appendAttributeValue(parser, enc, isCdata, next, end, pool, -+ account, &next); -+ } else { -+ OPEN_INTERNAL_ENTITY *const openEntity = parser->m_openAttributeEntities; -+ if (! openEntity) -+ return XML_ERROR_UNEXPECTED_STATE; -+ -+ ENTITY *const entity = openEntity->entity; -+ const char *const textStart -+ = ((const char *)entity->textPtr) + entity->processed; -+ const char *const textEnd -+ = (const char *)(entity->textPtr + entity->textLen); -+ /* Set a safe default value in case 'next' does not get set */ -+ const char *nextInEntity = textStart; -+ if (entity->hasMore) { -+ result = appendAttributeValue( -+ parser, parser->m_internalEncoding, isCdata, textStart, textEnd, -+ pool, XML_ACCOUNT_ENTITY_EXPANSION, &nextInEntity); -+ if (result != XML_ERROR_NONE) -+ break; -+ // Check if entity is complete, if not, mark down how much of it is -+ // processed. A XML_SUSPENDED check here is not required as -+ // appendAttributeValue will never suspend the parser. -+ if (textEnd != nextInEntity) { -+ entity->processed -+ = (int)(nextInEntity - (const char *)entity->textPtr); -+ continue; -+ } -+ -+ // Entity is complete. We cannot close it here since we need to first -+ // process its possible inner entities (which are added to the -+ // m_openAttributeEntities during appendAttributeValue) -+ entity->hasMore = XML_FALSE; -+ continue; -+ } // End of entity processing, "if" block skips the rest -+ -+ // Remove fully processed openEntity from open entity list. -+#if XML_GE == 1 -+ entityTrackingOnClose(parser, entity, __LINE__); -+#endif -+ // openEntity is m_openAttributeEntities' head, since we set it at the -+ // start of this function and because we skipped appendAttributeValue call -+ // with hasMore set to false. This means we can directly remove the head -+ // of m_openAttributeEntities -+ assert(parser->m_openAttributeEntities == openEntity); -+ entity->open = XML_FALSE; -+ parser->m_openAttributeEntities = parser->m_openAttributeEntities->next; -+ -+ /* put openEntity back in list of free instances */ -+ openEntity->next = parser->m_freeAttributeEntities; -+ parser->m_freeAttributeEntities = openEntity; -+ } -+ -+ // Break if an error occurred or there is nothing left to process -+ if (result || (parser->m_openAttributeEntities == NULL && end == next)) { -+ break; -+ } -+ } -+ - if (result) - return result; - if (! isCdata && poolLength(pool) && poolLastChar(pool) == 0x20) -@@ -5996,7 +6197,7 @@ storeAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata, - static enum XML_Error - appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata, - const char *ptr, const char *end, STRING_POOL *pool, -- enum XML_Account account) { -+ enum XML_Account account, const char **nextPtr) { - DTD *const dtd = parser->m_dtd; /* save one level of indirection */ - #ifndef XML_DTD - UNUSED_P(account); -@@ -6014,6 +6215,9 @@ appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata, - #endif - switch (tok) { - case XML_TOK_NONE: -+ if (nextPtr) { -+ *nextPtr = next; -+ } - return XML_ERROR_NONE; - case XML_TOK_INVALID: - if (enc == parser->m_encoding) -@@ -6154,21 +6358,11 @@ appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata, - return XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF; - } else { - enum XML_Error result; -- const XML_Char *textEnd = entity->textPtr + entity->textLen; -- entity->open = XML_TRUE; --#if XML_GE == 1 -- entityTrackingOnOpen(parser, entity, __LINE__); --#endif -- result = appendAttributeValue(parser, parser->m_internalEncoding, -- isCdata, (const char *)entity->textPtr, -- (const char *)textEnd, pool, -- XML_ACCOUNT_ENTITY_EXPANSION); --#if XML_GE == 1 -- entityTrackingOnClose(parser, entity, __LINE__); --#endif -- entity->open = XML_FALSE; -- if (result) -- return result; -+ result = processEntity(parser, entity, XML_FALSE, ENTITY_ATTRIBUTE); -+ if ((result == XML_ERROR_NONE) && (nextPtr != NULL)) { -+ *nextPtr = next; -+ } -+ return result; - } - } break; - default: -@@ -6197,7 +6391,7 @@ appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata, - static enum XML_Error - storeEntityValue(XML_Parser parser, const ENCODING *enc, - const char *entityTextPtr, const char *entityTextEnd, -- enum XML_Account account) { -+ enum XML_Account account, const char **nextPtr) { - DTD *const dtd = parser->m_dtd; /* save one level of indirection */ - STRING_POOL *pool = &(dtd->entityValuePool); - enum XML_Error result = XML_ERROR_NONE; -@@ -6215,8 +6409,9 @@ storeEntityValue(XML_Parser parser, const ENCODING *enc, - return XML_ERROR_NO_MEMORY; - } - -+ const char *next; - for (;;) { -- const char *next -+ next - = entityTextPtr; /* XmlEntityValueTok doesn't always set the last arg */ - int tok = XmlEntityValueTok(enc, entityTextPtr, entityTextEnd, &next); - -@@ -6278,16 +6473,8 @@ storeEntityValue(XML_Parser parser, const ENCODING *enc, - } else - dtd->keepProcessing = dtd->standalone; - } else { -- entity->open = XML_TRUE; -- entityTrackingOnOpen(parser, entity, __LINE__); -- result = storeEntityValue( -- parser, parser->m_internalEncoding, (const char *)entity->textPtr, -- (const char *)(entity->textPtr + entity->textLen), -- XML_ACCOUNT_ENTITY_EXPANSION); -- entityTrackingOnClose(parser, entity, __LINE__); -- entity->open = XML_FALSE; -- if (result) -- goto endEntityValue; -+ result = processEntity(parser, entity, XML_FALSE, ENTITY_VALUE); -+ goto endEntityValue; - } - break; - } -@@ -6375,6 +6562,81 @@ endEntityValue: - # ifdef XML_DTD - parser->m_prologState.inEntityValue = oldInEntityValue; - # endif /* XML_DTD */ -+ // If 'nextPtr' is given, it should be updated during the processing -+ if (nextPtr != NULL) { -+ *nextPtr = next; -+ } -+ return result; -+} -+ -+static enum XML_Error -+callStoreEntityValue(XML_Parser parser, const ENCODING *enc, -+ const char *entityTextPtr, const char *entityTextEnd, -+ enum XML_Account account) { -+ const char *next = entityTextPtr; -+ enum XML_Error result = XML_ERROR_NONE; -+ while (1) { -+ if (! parser->m_openValueEntities) { -+ result -+ = storeEntityValue(parser, enc, next, entityTextEnd, account, &next); -+ } else { -+ OPEN_INTERNAL_ENTITY *const openEntity = parser->m_openValueEntities; -+ if (! openEntity) -+ return XML_ERROR_UNEXPECTED_STATE; -+ -+ ENTITY *const entity = openEntity->entity; -+ const char *const textStart -+ = ((const char *)entity->textPtr) + entity->processed; -+ const char *const textEnd -+ = (const char *)(entity->textPtr + entity->textLen); -+ /* Set a safe default value in case 'next' does not get set */ -+ const char *nextInEntity = textStart; -+ if (entity->hasMore) { -+ result = storeEntityValue(parser, parser->m_internalEncoding, textStart, -+ textEnd, XML_ACCOUNT_ENTITY_EXPANSION, -+ &nextInEntity); -+ if (result != XML_ERROR_NONE) -+ break; -+ // Check if entity is complete, if not, mark down how much of it is -+ // processed. A XML_SUSPENDED check here is not required as -+ // appendAttributeValue will never suspend the parser. -+ if (textEnd != nextInEntity) { -+ entity->processed -+ = (int)(nextInEntity - (const char *)entity->textPtr); -+ continue; -+ } -+ -+ // Entity is complete. We cannot close it here since we need to first -+ // process its possible inner entities (which are added to the -+ // m_openValueEntities during storeEntityValue) -+ entity->hasMore = XML_FALSE; -+ continue; -+ } // End of entity processing, "if" block skips the rest -+ -+ // Remove fully processed openEntity from open entity list. -+# if XML_GE == 1 -+ entityTrackingOnClose(parser, entity, __LINE__); -+# endif -+ // openEntity is m_openValueEntities' head, since we set it at the -+ // start of this function and because we skipped storeEntityValue call -+ // with hasMore set to false. This means we can directly remove the head -+ // of m_openValueEntities -+ assert(parser->m_openValueEntities == openEntity); -+ entity->open = XML_FALSE; -+ parser->m_openValueEntities = parser->m_openValueEntities->next; -+ -+ /* put openEntity back in list of free instances */ -+ openEntity->next = parser->m_freeValueEntities; -+ parser->m_freeValueEntities = openEntity; -+ } -+ -+ // Break if an error occurred or there is nothing left to process -+ if (result -+ || (parser->m_openValueEntities == NULL && entityTextEnd == next)) { -+ break; -+ } -+ } -+ - return result; - } - -diff --git a/tests/alloc_tests.c b/tests/alloc_tests.c -index e5d46eb..12ea3b2 100644 ---- a/tests/alloc_tests.c -+++ b/tests/alloc_tests.c -@@ -19,6 +19,7 @@ - Copyright (c) 2020 Tim Gates - Copyright (c) 2021 Donghee Na - Copyright (c) 2023 Sony Corporation / Snild Dolkow -+ Copyright (c) 2025 Berkay Eren Ürün - Licensed under the MIT license: - - Permission is hereby granted, free of charge, to any person obtaining -@@ -450,6 +451,31 @@ START_TEST(test_alloc_internal_entity) { - } - END_TEST - -+START_TEST(test_alloc_parameter_entity) { -+ const char *text = "\">" -+ "%param1;" -+ "]> &internal;content"; -+ int i; -+ const int alloc_test_max_repeats = 30; -+ -+ for (i = 0; i < alloc_test_max_repeats; i++) { -+ g_allocation_count = i; -+ XML_SetParamEntityParsing(g_parser, XML_PARAM_ENTITY_PARSING_ALWAYS); -+ if (_XML_Parse_SINGLE_BYTES(g_parser, text, (int)strlen(text), XML_TRUE) -+ != XML_STATUS_ERROR) -+ break; -+ alloc_teardown(); -+ alloc_setup(); -+ } -+ g_allocation_count = -1; -+ if (i == 0) -+ fail("Parameter entity processed despite duff allocator"); -+ if (i == alloc_test_max_repeats) -+ fail("Parameter entity not processed at max allocation count"); -+} -+END_TEST -+ - /* Test the robustness against allocation failure of element handling - * Based on test_dtd_default_handling(). - */ -@@ -2079,6 +2105,7 @@ make_alloc_test_case(Suite *s) { - tcase_add_test__ifdef_xml_dtd(tc_alloc, test_alloc_external_entity); - tcase_add_test__ifdef_xml_dtd(tc_alloc, test_alloc_ext_entity_set_encoding); - tcase_add_test__ifdef_xml_dtd(tc_alloc, test_alloc_internal_entity); -+ tcase_add_test__ifdef_xml_dtd(tc_alloc, test_alloc_parameter_entity); - tcase_add_test__ifdef_xml_dtd(tc_alloc, test_alloc_dtd_default_handling); - tcase_add_test(tc_alloc, test_alloc_explicit_encoding); - tcase_add_test(tc_alloc, test_alloc_set_base); -diff --git a/tests/basic_tests.c b/tests/basic_tests.c -index d38b8fd..f0025fc 100644 ---- a/tests/basic_tests.c -+++ b/tests/basic_tests.c -@@ -10,7 +10,7 @@ - Copyright (c) 2003 Greg Stein - Copyright (c) 2005-2007 Steven Solie - Copyright (c) 2005-2012 Karl Waclawek -- Copyright (c) 2016-2024 Sebastian Pipping -+ Copyright (c) 2016-2025 Sebastian Pipping - Copyright (c) 2017-2022 Rhodri James - Copyright (c) 2017 Joe Orton - Copyright (c) 2017 José Gutiérrez de la Concha -@@ -19,6 +19,7 @@ - Copyright (c) 2020 Tim Gates - Copyright (c) 2021 Donghee Na - Copyright (c) 2023-2024 Sony Corporation / Snild Dolkow -+ Copyright (c) 2024-2025 Berkay Eren Ürün - Licensed under the MIT license: - - Permission is hereby granted, free of charge, to any person obtaining -@@ -3960,7 +3961,7 @@ START_TEST(test_skipped_null_loaded_ext_entity) { - = {"\n" - "\n" - "%pe2;\n", -- external_entity_null_loader}; -+ external_entity_null_loader, NULL}; - - XML_SetUserData(g_parser, &test_data); - XML_SetParamEntityParsing(g_parser, XML_PARAM_ENTITY_PARSING_ALWAYS); -@@ -3978,7 +3979,7 @@ START_TEST(test_skipped_unloaded_ext_entity) { - = {"\n" - "\n" - "%pe2;\n", -- NULL}; -+ NULL, NULL}; - - XML_SetUserData(g_parser, &test_data); - XML_SetParamEntityParsing(g_parser, XML_PARAM_ENTITY_PARSING_ALWAYS); -@@ -5278,6 +5279,151 @@ START_TEST(test_pool_integrity_with_unfinished_attr) { - } - END_TEST - -+/* Test a possible early return location in internalEntityProcessor */ -+START_TEST(test_entity_ref_no_elements) { -+ const char *const text = "\n" -+ "]> &e1;"; // intentionally missing newline -+ -+ XML_Parser parser = XML_ParserCreate(NULL); -+ assert_true(_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE) -+ == XML_STATUS_ERROR); -+ assert_true(XML_GetErrorCode(parser) == XML_ERROR_NO_ELEMENTS); -+ XML_ParserFree(parser); -+} -+END_TEST -+ -+/* Tests if chained entity references lead to unbounded recursion */ -+START_TEST(test_deep_nested_entity) { -+ const size_t N_LINES = 60000; -+ const size_t SIZE_PER_LINE = 50; -+ -+ char *const text = (char *)malloc((N_LINES + 4) * SIZE_PER_LINE); -+ if (text == NULL) { -+ fail("malloc failed"); -+ } -+ -+ char *textPtr = text; -+ -+ // Create the XML -+ textPtr += snprintf(textPtr, SIZE_PER_LINE, -+ "\n"); -+ -+ for (size_t i = 1; i < N_LINES; ++i) { -+ textPtr += snprintf(textPtr, SIZE_PER_LINE, " \n", -+ (long unsigned)i, (long unsigned)(i - 1)); -+ } -+ -+ snprintf(textPtr, SIZE_PER_LINE, "]> &s%lu;\n", -+ (long unsigned)(N_LINES - 1)); -+ -+ const XML_Char *const expected = XCS("deepText"); -+ -+ CharData storage; -+ CharData_Init(&storage); -+ -+ XML_Parser parser = XML_ParserCreate(NULL); -+ -+ XML_SetCharacterDataHandler(parser, accumulate_characters); -+ XML_SetUserData(parser, &storage); -+ -+ if (_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE) -+ == XML_STATUS_ERROR) -+ xml_failure(parser); -+ -+ CharData_CheckXMLChars(&storage, expected); -+ XML_ParserFree(parser); -+ free(text); -+} -+END_TEST -+ -+/* Tests if chained entity references in attributes -+lead to unbounded recursion */ -+START_TEST(test_deep_nested_attribute_entity) { -+ const size_t N_LINES = 60000; -+ const size_t SIZE_PER_LINE = 100; -+ -+ char *const text = (char *)malloc((N_LINES + 4) * SIZE_PER_LINE); -+ if (text == NULL) { -+ fail("malloc failed"); -+ } -+ -+ char *textPtr = text; -+ -+ // Create the XML -+ textPtr += snprintf(textPtr, SIZE_PER_LINE, -+ "\n"); -+ -+ for (size_t i = 1; i < N_LINES; ++i) { -+ textPtr += snprintf(textPtr, SIZE_PER_LINE, " \n", -+ (long unsigned)i, (long unsigned)(i - 1)); -+ } -+ -+ snprintf(textPtr, SIZE_PER_LINE, "]> mainText\n", -+ (long unsigned)(N_LINES - 1)); -+ -+ AttrInfo doc_info[] = {{XCS("name"), XCS("deepText")}, {NULL, NULL}}; -+ ElementInfo info[] = {{XCS("foo"), 1, NULL, NULL}, {NULL, 0, NULL, NULL}}; -+ info[0].attributes = doc_info; -+ -+ XML_Parser parser = XML_ParserCreate(NULL); -+ ParserAndElementInfo parserPlusElemenInfo = {parser, info}; -+ -+ XML_SetStartElementHandler(parser, counting_start_element_handler); -+ XML_SetUserData(parser, &parserPlusElemenInfo); -+ -+ if (_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE) -+ == XML_STATUS_ERROR) -+ xml_failure(parser); -+ -+ XML_ParserFree(parser); -+ free(text); -+} -+END_TEST -+ -+START_TEST(test_deep_nested_entity_delayed_interpretation) { -+ const size_t N_LINES = 70000; -+ const size_t SIZE_PER_LINE = 100; -+ -+ char *const text = (char *)malloc((N_LINES + 4) * SIZE_PER_LINE); -+ if (text == NULL) { -+ fail("malloc failed"); -+ } -+ -+ char *textPtr = text; -+ -+ // Create the XML -+ textPtr += snprintf(textPtr, SIZE_PER_LINE, -+ "\n"); -+ -+ for (size_t i = 1; i < N_LINES; ++i) { -+ textPtr += snprintf(textPtr, SIZE_PER_LINE, -+ " \n", (long unsigned)i, -+ (long unsigned)(i - 1)); -+ } -+ -+ snprintf(textPtr, SIZE_PER_LINE, -+ " \">\n" -+ " %%define_g;\n" -+ "]>\n" -+ "\n", -+ (long unsigned)(N_LINES - 1)); -+ -+ XML_Parser parser = XML_ParserCreate(NULL); -+ -+ XML_SetParamEntityParsing(parser, XML_PARAM_ENTITY_PARSING_ALWAYS); -+ if (_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE) -+ == XML_STATUS_ERROR) -+ xml_failure(parser); -+ -+ XML_ParserFree(parser); -+ free(text); -+} -+END_TEST -+ - START_TEST(test_nested_entity_suspend) { - const char *const text = "'>\n" -@@ -5308,6 +5454,35 @@ START_TEST(test_nested_entity_suspend) { - } - END_TEST - -+START_TEST(test_nested_entity_suspend_2) { -+ const char *const text = "\n" -+ " \n" -+ " \n" -+ "]>\n" -+ "&ge3;"; -+ const XML_Char *const expected = XCS("head3") XCS("head2") XCS("head1") -+ XCS("Z") XCS("tail1") XCS("tail2") XCS("tail3"); -+ CharData storage; -+ CharData_Init(&storage); -+ XML_Parser parser = XML_ParserCreate(NULL); -+ ParserPlusStorage parserPlusStorage = {parser, &storage}; -+ -+ XML_SetCharacterDataHandler(parser, accumulate_char_data_and_suspend); -+ XML_SetUserData(parser, &parserPlusStorage); -+ -+ enum XML_Status status = XML_Parse(parser, text, (int)strlen(text), XML_TRUE); -+ while (status == XML_STATUS_SUSPENDED) { -+ status = XML_ResumeParser(parser); -+ } -+ if (status != XML_STATUS_OK) -+ xml_failure(parser); -+ -+ CharData_CheckXMLChars(&storage, expected); -+ XML_ParserFree(parser); -+} -+END_TEST -+ - /* Regression test for quadratic parsing on large tokens */ - START_TEST(test_big_tokens_scale_linearly) { - const struct { -@@ -6147,7 +6322,13 @@ make_basic_test_case(Suite *s) { - tcase_add_test(tc_basic, test_empty_element_abort); - tcase_add_test__ifdef_xml_dtd(tc_basic, - test_pool_integrity_with_unfinished_attr); -+ tcase_add_test__if_xml_ge(tc_basic, test_entity_ref_no_elements); -+ tcase_add_test__if_xml_ge(tc_basic, test_deep_nested_entity); -+ tcase_add_test__if_xml_ge(tc_basic, test_deep_nested_attribute_entity); -+ tcase_add_test__if_xml_ge(tc_basic, -+ test_deep_nested_entity_delayed_interpretation); - tcase_add_test__if_xml_ge(tc_basic, test_nested_entity_suspend); -+ tcase_add_test__if_xml_ge(tc_basic, test_nested_entity_suspend_2); - tcase_add_test(tc_basic, test_big_tokens_scale_linearly); - tcase_add_test(tc_basic, test_set_reparse_deferral); - tcase_add_test(tc_basic, test_reparse_deferral_is_inherited); -diff --git a/tests/handlers.c b/tests/handlers.c -index 0211985..bdb5b0e 100644 ---- a/tests/handlers.c -+++ b/tests/handlers.c -@@ -1882,6 +1882,21 @@ accumulate_entity_decl(void *userData, const XML_Char *entityName, - CharData_AppendXMLChars(storage, XCS("\n"), 1); - } - -+ -+void XMLCALL -+accumulate_char_data_and_suspend(void *userData, const XML_Char *s, int len) { -+ ParserPlusStorage *const parserPlusStorage = (ParserPlusStorage *)userData; -+ -+ CharData_AppendXMLChars(parserPlusStorage->storage, s, len); -+ -+ for (int i = 0; i < len; i++) { -+ if (s[i] == 'Z') { -+ XML_StopParser(parserPlusStorage->parser, /*resumable=*/XML_TRUE); -+ break; -+ } -+ } -+} -+ - void XMLCALL - accumulate_start_element(void *userData, const XML_Char *name, - const XML_Char **atts) { -diff --git a/tests/handlers.h b/tests/handlers.h -index 8850bb9..4d6a08d 100644 ---- a/tests/handlers.h -+++ b/tests/handlers.h -@@ -325,6 +325,7 @@ extern int XMLCALL external_entity_devaluer(XML_Parser parser, - typedef struct ext_hdlr_data { - const char *parse_text; - XML_ExternalEntityRefHandler handler; -+ CharData *storage; - } ExtHdlrData; - - extern int XMLCALL external_entity_oneshot_loader(XML_Parser parser, -@@ -569,6 +570,10 @@ extern void XMLCALL accumulate_entity_decl( - const XML_Char *systemId, const XML_Char *publicId, - const XML_Char *notationName); - -+extern void XMLCALL accumulate_char_data_and_suspend(void *userData, -+ const XML_Char *s, -+ int len); -+ - extern void XMLCALL accumulate_start_element(void *userData, - const XML_Char *name, - const XML_Char **atts); -diff --git a/tests/misc_tests.c b/tests/misc_tests.c -index 9afe092..f9a78f6 100644 ---- a/tests/misc_tests.c -+++ b/tests/misc_tests.c -@@ -59,6 +59,9 @@ - #include "handlers.h" - #include "misc_tests.h" - -+void XMLCALL accumulate_characters_ext_handler(void *userData, -+ const XML_Char *s, int len); -+ - /* Test that a failure to allocate the parser structure fails gracefully */ - START_TEST(test_misc_alloc_create_parser) { - XML_Memory_Handling_Suite memsuite = {duff_allocator, realloc, free}; -@@ -519,6 +522,45 @@ START_TEST(test_misc_stopparser_rejects_unstarted_parser) { - } - END_TEST - -+/* Adaptation of accumulate_characters that takes ExtHdlrData input to work with -+ * test_renter_loop_finite_content below */ -+void XMLCALL -+accumulate_characters_ext_handler(void *userData, const XML_Char *s, int len) { -+ ExtHdlrData *const test_data = (ExtHdlrData *)userData; -+ CharData_AppendXMLChars(test_data->storage, s, len); -+} -+ -+/* Test that internalEntityProcessor does not re-enter forever; -+ * based on files tests/xmlconf/xmltest/valid/ext-sa/012.{xml,ent} */ -+START_TEST(test_renter_loop_finite_content) { -+ CharData storage; -+ CharData_Init(&storage); -+ const char *const text = "\n" -+ "\n" -+ "\n" -+ "\n" -+ "\n" -+ "\n" -+ "]>\n" -+ "&e1;\n"; -+ ExtHdlrData test_data = {"&e4;\n", external_entity_null_loader, &storage}; -+ const XML_Char *const expected = XCS("(e5)\n"); -+ -+ XML_Parser parser = XML_ParserCreate(NULL); -+ assert_true(parser != NULL); -+ XML_SetUserData(parser, &test_data); -+ XML_SetExternalEntityRefHandler(parser, external_entity_oneshot_loader); -+ XML_SetCharacterDataHandler(parser, accumulate_characters_ext_handler); -+ if (_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE) -+ == XML_STATUS_ERROR) -+ xml_failure(parser); -+ -+ CharData_CheckXMLChars(&storage, expected); -+ XML_ParserFree(parser); -+} -+END_TEST -+ - void - make_miscellaneous_test_case(Suite *s) { - TCase *tc_misc = tcase_create("miscellaneous tests"); -@@ -545,4 +587,5 @@ make_miscellaneous_test_case(Suite *s) { - tcase_add_test(tc_misc, test_misc_char_handler_stop_without_leak); - tcase_add_test(tc_misc, test_misc_resumeparser_not_crashing); - tcase_add_test(tc_misc, test_misc_stopparser_rejects_unstarted_parser); -+ tcase_add_test__if_xml_ge(tc_misc, test_renter_loop_finite_content); - } --- -2.48.1.431.g5a526e5e18 - diff --git a/SPECS/expat/CVE-2025-59375.patch b/SPECS/expat/CVE-2025-59375.patch deleted file mode 100644 index 23b0c12c368..00000000000 --- a/SPECS/expat/CVE-2025-59375.patch +++ /dev/null @@ -1,1691 +0,0 @@ -From 0872c189db6e457084fca335662a9cb49e8ec4c7 Mon Sep 17 00:00:00 2001 -From: Sebastian Pipping -Date: Mon, 1 Sep 2025 18:06:59 +0200 - -Upstream Patch Reference: https://patch-diff.githubusercontent.com/raw/libexpat/libexpat/pull/1034.diff.patch -Upstream PR: https://github.com/libexpat/libexpat/pull/1034 - -Modified patch to apply to AzureLinux -Modified by: akhila-guruju -Date: Mon, 22 Sep 2025 11:33:11 +0000 -Subject: [PATCH] Address CVE-2025-59375 - ---- - doc/reference.html | 118 +++++++- - doc/xmlwf.1 | 30 +- - doc/xmlwf.xml | 26 +- - fuzz/xml_parse_fuzzer.c | 14 +- - fuzz/xml_parsebuffer_fuzzer.c | 14 +- - lib/expat.h | 15 +- - lib/internal.h | 8 + - lib/libexpat.def.cmake | 3 + - lib/xmlparse.c | 521 ++++++++++++++++++++++++++++------ - tests/alloc_tests.c | 214 ++++++++++++++ - tests/basic_tests.c | 4 + - tests/nsalloc_tests.c | 5 + - xmlwf/xmlwf.c | 11 +- - xmlwf/xmlwf_helpgen.py | 3 + - 14 files changed, 874 insertions(+), 112 deletions(-) - -diff --git a/doc/reference.html b/doc/reference.html -index c2ae9bb..8f14b01 100644 ---- a/doc/reference.html -+++ b/doc/reference.html -@@ -157,6 +157,8 @@ interface.

- - -@@ -1900,7 +1902,7 @@ struct XML_cp { -

Sets a handler for element declarations in a DTD. The handler gets - called with the name of the element in the declaration and a pointer - to a structure that contains the element model. It's the user code's --responsibility to free model when finished with it. See -+responsibility to free model when finished with via a call to - XML_FreeContentModel. - There is no need to free the model from the handler, it can be kept - around and freed at a later stage.

-@@ -2262,6 +2264,120 @@ XML_SetBillionLaughsAttackProtectionActivationThreshold(XML_Parser p, -

- - -+

XML_SetAllocTrackerMaximumAmplification

-+
-+/* Added in Expat 2.7.2. */
-+XML_Bool
-+XML_SetAllocTrackerMaximumAmplification(XML_Parser p,
-+                                        float maximumAmplificationFactor);
-+
-+
-+

-+ Sets the maximum tolerated amplification factor -+ between direct input and bytes of dynamic memory allocated -+ (default: 100.0) -+ of parser p to maximumAmplificationFactor, and -+ returns XML_TRUE upon success and XML_FALSE upon error. -+

-+ -+

-+ Note: -+ There are three types of allocations that intentionally bypass tracking and limiting: -+

-+ -+ -+

The amplification factor is calculated as ..

-+
amplification := allocated / direct
-+

-+ .. while parsing, whereas -+ direct is the number of bytes read from the primary document in parsing and -+ allocated is the number of bytes of dynamic memory allocated in the parser hierarchy. -+

-+ -+

For a call to XML_SetAllocTrackerMaximumAmplification to succeed:

-+
    -+
  • parser p must be a non-NULL root parser (without any parent parsers) and
  • -+
  • maximumAmplificationFactor must be non-NaN and greater than or equal to 1.0.
  • -+
-+ -+

-+ Note: -+ If you ever need to increase this value for non-attack payload, -+ please file a bug report. -+

-+ -+

-+ Note: -+ Amplifications factors greater than 100 can been observed near the start of parsing -+ even with benign files in practice. -+ -+ So if you do reduce the maximum allowed amplification, -+ please make sure that the activation threshold is still big enough -+ to not end up with undesired false positives (i.e. benign files being rejected). -+

-+
-+ -+

XML_SetAllocTrackerActivationThreshold

-+
-+/* Added in Expat 2.7.2. */
-+XML_Bool
-+XML_SetAllocTrackerActivationThreshold(XML_Parser p,
-+                                       unsigned long long activationThresholdBytes);
-+
-+
-+

-+ Sets number of allocated bytes of dynamic memory -+ needed to activate protection against disproportionate use of RAM -+ (default: 64 MiB) -+ of parser p to activationThresholdBytes, and -+ returns XML_TRUE upon success and XML_FALSE upon error. -+

-+ -+

-+ Note: -+ For types of allocations that intentionally bypass tracking and limiting, please see -+ XML_SetAllocTrackerMaximumAmplification -+ above. -+

-+ -+

For a call to XML_SetAllocTrackerActivationThreshold to succeed:

-+
    -+
  • parser p must be a non-NULL root parser (without any parent parsers).
  • -+
-+ -+

-+ Note: -+ If you ever need to increase this value for non-attack payload, -+ please file a bug report. -+

-+
-+ -

XML_SetReparseDeferralEnabled

-
- /* Added in Expat 2.6.0. */
-diff --git a/doc/xmlwf.1 b/doc/xmlwf.1
-index 61b3025..5f50ba9 100644
---- a/doc/xmlwf.1
-+++ b/doc/xmlwf.1
-@@ -5,7 +5,7 @@
- \\$2 \(la\\$1\(ra\\$3
- ..
- .if \n(.g .mso www.tmac
--.TH XMLWF 1 "November 6, 2024" "" ""
-+.TH XMLWF 1 "September 16, 2025" "" ""
- .SH NAME
- xmlwf \- Determines if an XML document is well-formed
- .SH SYNOPSIS
-@@ -88,7 +88,11 @@ supports both.
- .TP 
- \*(T<\fB\-a\fR\*(T> \fIfactor\fR
- Sets the maximum tolerated amplification factor
--for protection against billion laughs attacks (default: 100.0).
-+for protection against amplification attacks
-+like the billion laughs attack
-+(default: 100.0
-+for the sum of direct and indirect output and also
-+for allocations of dynamic memory).
- The amplification factor is calculated as ..
- 
- .nf
-@@ -97,12 +101,22 @@ The amplification factor is calculated as ..
-           
- .fi
- 
--\&.. while parsing, whereas
-+\&.. with regard to use of entities and ..
-+
-+.nf
-+
-+            amplification := allocated / direct
-+          
-+.fi
-+
-+\&.. with regard to dynamic memory while parsing.
-  is the number of bytes read
--from the primary document in parsing and
-+from the primary document in parsing,
-  is the number of bytes
- added by expanding entities and reading of external DTD files,
--combined.
-+combined, and
-+ is the total number of bytes of dynamic memory
-+allocated (and not freed) per hierarchy of parsers.
- 
- \fINOTE\fR:
- If you ever need to increase this value for non-attack payload,
-@@ -110,8 +124,10 @@ please file a bug report.
- .TP 
- \*(T<\fB\-b\fR\*(T> \fIbytes\fR
- Sets the number of output bytes (including amplification)
--needed to activate protection against billion laughs attacks
--(default: 8 MiB).
-+needed to activate protection against amplification attacks
-+like billion laughs
-+(default: 8 MiB for the sum of direct and indirect output,
-+and 64 MiB for allocations of dynamic memory).
- This can be thought of as an "activation threshold".
- 
- \fINOTE\fR:
-diff --git a/doc/xmlwf.xml b/doc/xmlwf.xml
-index cf6d984..d152e6f 100644
---- a/doc/xmlwf.xml
-+++ b/doc/xmlwf.xml
-@@ -158,19 +158,31 @@ supports both.
-         
-           
-             Sets the maximum tolerated amplification factor
--            for protection against billion laughs attacks (default: 100.0).
-+            for protection against amplification attacks
-+            like the billion laughs attack
-+            (default: 100.0
-+            for the sum of direct and indirect output and also
-+            for allocations of dynamic memory).
-             The amplification factor is calculated as ..
-           
-           
-             amplification := (direct + indirect) / direct
-           
-           
--            .. while parsing, whereas
-+            .. with regard to use of entities and ..
-+          
-+          
-+            amplification := allocated / direct
-+          
-+          
-+            .. with regard to dynamic memory while parsing.
-             <direct> is the number of bytes read
--              from the primary document in parsing and
-+              from the primary document in parsing,
-             <indirect> is the number of bytes
-               added by expanding entities and reading of external DTD files,
--              combined.
-+              combined, and
-+            <allocated> is the total number of bytes of dynamic memory
-+              allocated (and not freed) per hierarchy of parsers.
-           
-           
-             NOTE:
-@@ -185,8 +197,10 @@ supports both.
-         
-           
-             Sets the number of output bytes (including amplification)
--            needed to activate protection against billion laughs attacks
--            (default: 8 MiB).
-+            needed to activate protection against amplification attacks
-+            like billion laughs
-+            (default: 8 MiB for the sum of direct and indirect output,
-+            and 64 MiB for allocations of dynamic memory).
-             This can be thought of as an "activation threshold".
-           
-           
-diff --git a/fuzz/xml_parse_fuzzer.c b/fuzz/xml_parse_fuzzer.c
-index a7e8414..677fe59 100644
---- a/fuzz/xml_parse_fuzzer.c
-+++ b/fuzz/xml_parse_fuzzer.c
-@@ -89,15 +89,17 @@ LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
- 
-   XML_Parser externalEntityParser
-       = XML_ExternalEntityParserCreate(parentParser, "e1", NULL);
--  assert(externalEntityParser);
--  ParseOneInput(externalEntityParser, data, size);
--  XML_ParserFree(externalEntityParser);
-+  if (externalEntityParser != NULL) {
-+    ParseOneInput(externalEntityParser, data, size);
-+    XML_ParserFree(externalEntityParser);
-+  }
- 
-   XML_Parser externalDtdParser
-       = XML_ExternalEntityParserCreate(parentParser, NULL, NULL);
--  assert(externalDtdParser);
--  ParseOneInput(externalDtdParser, data, size);
--  XML_ParserFree(externalDtdParser);
-+  if (externalDtdParser != NULL) {
-+    ParseOneInput(externalDtdParser, data, size);
-+    XML_ParserFree(externalDtdParser);
-+  }
- 
-   // finally frees this parser which served as parent
-   XML_ParserFree(parentParser);
-diff --git a/fuzz/xml_parsebuffer_fuzzer.c b/fuzz/xml_parsebuffer_fuzzer.c
-index 0327aa9..7939f20 100644
---- a/fuzz/xml_parsebuffer_fuzzer.c
-+++ b/fuzz/xml_parsebuffer_fuzzer.c
-@@ -101,15 +101,17 @@ LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
- 
-   XML_Parser externalEntityParser
-       = XML_ExternalEntityParserCreate(parentParser, "e1", NULL);
--  assert(externalEntityParser);
--  ParseOneInput(externalEntityParser, data, size);
--  XML_ParserFree(externalEntityParser);
-+  if (externalEntityParser != NULL) {
-+    ParseOneInput(externalEntityParser, data, size);
-+    XML_ParserFree(externalEntityParser);
-+  }
- 
-   XML_Parser externalDtdParser
-       = XML_ExternalEntityParserCreate(parentParser, NULL, NULL);
--  assert(externalDtdParser);
--  ParseOneInput(externalDtdParser, data, size);
--  XML_ParserFree(externalDtdParser);
-+  if (externalDtdParser != NULL) {
-+    ParseOneInput(externalDtdParser, data, size);
-+    XML_ParserFree(externalDtdParser);
-+  }
- 
-   // finally frees this parser which served as parent
-   XML_ParserFree(parentParser);
-diff --git a/lib/expat.h b/lib/expat.h
-index 523b37d..df207e9 100644
---- a/lib/expat.h
-+++ b/lib/expat.h
-@@ -1032,7 +1032,10 @@ enum XML_FeatureEnum {
-   XML_FEATURE_BILLION_LAUGHS_ATTACK_PROTECTION_MAXIMUM_AMPLIFICATION_DEFAULT,
-   XML_FEATURE_BILLION_LAUGHS_ATTACK_PROTECTION_ACTIVATION_THRESHOLD_DEFAULT,
-   /* Added in Expat 2.6.0. */
--  XML_FEATURE_GE
-+  XML_FEATURE_GE,
-+  /* Added in Expat 2.7.2. */
-+  XML_FEATURE_ALLOC_TRACKER_MAXIMUM_AMPLIFICATION_DEFAULT,
-+  XML_FEATURE_ALLOC_TRACKER_ACTIVATION_THRESHOLD_DEFAULT,
-   /* Additional features must be added to the end of this enum. */
- };
- 
-@@ -1057,6 +1060,16 @@ XML_SetBillionLaughsAttackProtectionMaximumAmplification(
- XMLPARSEAPI(XML_Bool)
- XML_SetBillionLaughsAttackProtectionActivationThreshold(
-     XML_Parser parser, unsigned long long activationThresholdBytes);
-+
-+/* Added in Expat 2.7.2. */
-+XMLPARSEAPI(XML_Bool)
-+XML_SetAllocTrackerMaximumAmplification(XML_Parser parser,
-+                                        float maximumAmplificationFactor);
-+
-+/* Added in Expat 2.7.2. */
-+XMLPARSEAPI(XML_Bool)
-+XML_SetAllocTrackerActivationThreshold(
-+    XML_Parser parser, unsigned long long activationThresholdBytes);
- #endif
- 
- /* Added in Expat 2.6.0. */
-diff --git a/lib/internal.h b/lib/internal.h
-index 167ec36..1b763ff 100644
---- a/lib/internal.h
-+++ b/lib/internal.h
-@@ -145,6 +145,11 @@
-   100.0f
- #define EXPAT_BILLION_LAUGHS_ATTACK_PROTECTION_ACTIVATION_THRESHOLD_DEFAULT    \
-   8388608 // 8 MiB, 2^23
-+
-+#define EXPAT_ALLOC_TRACKER_MAXIMUM_AMPLIFICATION_DEFAULT 100.0f
-+#define EXPAT_ALLOC_TRACKER_ACTIVATION_THRESHOLD_DEFAULT                       \
-+  67108864 // 64 MiB, 2^26
-+
- /* NOTE END */
- 
- #include "expat.h" // so we can use type XML_Parser below
-@@ -168,6 +173,9 @@ extern
- #endif
-     XML_Bool g_reparseDeferralEnabledDefault; // written ONLY in runtests.c
- #if defined(XML_TESTING)
-+void *expat_malloc(XML_Parser parser, size_t size, int sourceLine);
-+void expat_free(XML_Parser parser, void *ptr, int sourceLine);
-+void *expat_realloc(XML_Parser parser, void *ptr, size_t size, int sourceLine);
- extern unsigned int g_bytesScanned; // used for testing only
- #endif
- 
-diff --git a/lib/libexpat.def.cmake b/lib/libexpat.def.cmake
-index 10ee9cd..7a3a7ec 100644
---- a/lib/libexpat.def.cmake
-+++ b/lib/libexpat.def.cmake
-@@ -79,3 +79,6 @@ EXPORTS
- @_EXPAT_COMMENT_DTD_OR_GE@ XML_SetBillionLaughsAttackProtectionMaximumAmplification @70
- ; added with version 2.6.0
-   XML_SetReparseDeferralEnabled @71
-+; added with version 2.7.2
-+@_EXPAT_COMMENT_DTD_OR_GE@ XML_SetAllocTrackerMaximumAmplification @72
-+@_EXPAT_COMMENT_DTD_OR_GE@ XML_SetAllocTrackerActivationThreshold @73
-diff --git a/lib/xmlparse.c b/lib/xmlparse.c
-index 473c791..e2847b1 100644
---- a/lib/xmlparse.c
-+++ b/lib/xmlparse.c
-@@ -234,7 +234,7 @@ typedef struct {
-   unsigned char power;
-   size_t size;
-   size_t used;
--  const XML_Memory_Handling_Suite *mem;
-+  XML_Parser parser;
- } HASH_TABLE;
- 
- static size_t keylen(KEY s);
-@@ -357,7 +357,7 @@ typedef struct {
-   const XML_Char *end;
-   XML_Char *ptr;
-   XML_Char *start;
--  const XML_Memory_Handling_Suite *mem;
-+  XML_Parser parser;
- } STRING_POOL;
- 
- /* The XML_Char before the name is used to determine whether
-@@ -452,6 +452,14 @@ typedef struct accounting {
-   unsigned long long activationThresholdBytes;
- } ACCOUNTING;
- 
-+typedef struct MALLOC_TRACKER {
-+  XmlBigCount bytesAllocated;
-+  XmlBigCount peakBytesAllocated; // updated live only for debug level >=2
-+  unsigned long debugLevel;
-+  float maximumAmplificationFactor; // >=1.0
-+  XmlBigCount activationThresholdBytes;
-+} MALLOC_TRACKER;
-+
- typedef struct entity_stats {
-   unsigned int countEverOpened;
-   unsigned int currentDepth;
-@@ -555,27 +563,24 @@ static XML_Bool setContext(XML_Parser parser, const XML_Char *context);
- 
- static void FASTCALL normalizePublicId(XML_Char *s);
- 
--static DTD *dtdCreate(const XML_Memory_Handling_Suite *ms);
-+static DTD *dtdCreate(XML_Parser parser);
- /* do not call if m_parentParser != NULL */
--static void dtdReset(DTD *p, const XML_Memory_Handling_Suite *ms);
--static void dtdDestroy(DTD *p, XML_Bool isDocEntity,
--                       const XML_Memory_Handling_Suite *ms);
-+static void dtdReset(DTD *p, XML_Parser parser);
-+static void dtdDestroy(DTD *p, XML_Bool isDocEntity, XML_Parser parser);
- static int dtdCopy(XML_Parser oldParser, DTD *newDtd, const DTD *oldDtd,
--                   const XML_Memory_Handling_Suite *ms);
-+                   XML_Parser parser);
- static int copyEntityTable(XML_Parser oldParser, HASH_TABLE *newTable,
-                            STRING_POOL *newPool, const HASH_TABLE *oldTable);
- static NAMED *lookup(XML_Parser parser, HASH_TABLE *table, KEY name,
-                      size_t createSize);
--static void FASTCALL hashTableInit(HASH_TABLE *table,
--                                   const XML_Memory_Handling_Suite *ms);
-+static void FASTCALL hashTableInit(HASH_TABLE *table, XML_Parser parser);
- static void FASTCALL hashTableClear(HASH_TABLE *table);
- static void FASTCALL hashTableDestroy(HASH_TABLE *table);
- static void FASTCALL hashTableIterInit(HASH_TABLE_ITER *iter,
-                                        const HASH_TABLE *table);
- static NAMED *FASTCALL hashTableIterNext(HASH_TABLE_ITER *iter);
- 
--static void FASTCALL poolInit(STRING_POOL *pool,
--                              const XML_Memory_Handling_Suite *ms);
-+static void FASTCALL poolInit(STRING_POOL *pool, XML_Parser parser);
- static void FASTCALL poolClear(STRING_POOL *pool);
- static void FASTCALL poolDestroy(STRING_POOL *pool);
- static XML_Char *poolAppend(STRING_POOL *pool, const ENCODING *enc,
-@@ -595,15 +600,15 @@ static XML_Content *build_model(XML_Parser parser);
- static ELEMENT_TYPE *getElementType(XML_Parser parser, const ENCODING *enc,
-                                     const char *ptr, const char *end);
- 
--static XML_Char *copyString(const XML_Char *s,
--                            const XML_Memory_Handling_Suite *memsuite);
-+static XML_Char *copyString(const XML_Char *s, XML_Parser parser);
- 
- static unsigned long generate_hash_secret_salt(XML_Parser parser);
- static XML_Bool startParsing(XML_Parser parser);
- 
- static XML_Parser parserCreate(const XML_Char *encodingName,
-                                const XML_Memory_Handling_Suite *memsuite,
--                               const XML_Char *nameSep, DTD *dtd);
-+                               const XML_Char *nameSep, DTD *dtd,
-+                               XML_Parser parentParser);
- 
- static void parserInit(XML_Parser parser, const XML_Char *encodingName);
- 
-@@ -773,14 +778,232 @@ struct XML_ParserStruct {
-   unsigned long m_hash_secret_salt;
- #if XML_GE == 1
-   ACCOUNTING m_accounting;
-+  MALLOC_TRACKER m_alloc_tracker;
-   ENTITY_STATS m_entity_stats;
- #endif
-   XML_Bool m_reenter;
- };
- 
--#define MALLOC(parser, s) (parser->m_mem.malloc_fcn((s)))
--#define REALLOC(parser, p, s) (parser->m_mem.realloc_fcn((p), (s)))
--#define FREE(parser, p) (parser->m_mem.free_fcn((p)))
-+#if XML_GE == 1
-+#  define MALLOC(parser, s) (expat_malloc((parser), (s), __LINE__))
-+#  define REALLOC(parser, p, s) (expat_realloc((parser), (p), (s), __LINE__))
-+#  define FREE(parser, p) (expat_free((parser), (p), __LINE__))
-+#else
-+#  define MALLOC(parser, s) (parser->m_mem.malloc_fcn((s)))
-+#  define REALLOC(parser, p, s) (parser->m_mem.realloc_fcn((p), (s)))
-+#  define FREE(parser, p) (parser->m_mem.free_fcn((p)))
-+#endif
-+
-+#if XML_GE == 1
-+static void
-+expat_heap_stat(XML_Parser rootParser, char operator, XmlBigCount absDiff,
-+                XmlBigCount newTotal, XmlBigCount peakTotal, int sourceLine) {
-+  // NOTE: This can be +infinity or -nan
-+  const float amplification
-+      = (float)newTotal / (float)rootParser->m_accounting.countBytesDirect;
-+  fprintf(
-+      stderr,
-+      "expat: Allocations(%p): Direct " EXPAT_FMT_ULL("10") ", allocated %c" EXPAT_FMT_ULL(
-+          "10") " to " EXPAT_FMT_ULL("10") " (" EXPAT_FMT_ULL("10") " peak), amplification %8.2f (xmlparse.c:%d)\n",
-+      (void *)rootParser, rootParser->m_accounting.countBytesDirect, operator,
-+      absDiff, newTotal, peakTotal, (double)amplification, sourceLine);
-+}
-+
-+static bool
-+expat_heap_increase_tolerable(XML_Parser rootParser, XmlBigCount increase,
-+                              int sourceLine) {
-+  assert(rootParser != NULL);
-+  assert(increase > 0);
-+
-+  XmlBigCount newTotal = 0;
-+  bool tolerable = true;
-+
-+  // Detect integer overflow
-+  if ((XmlBigCount)-1 - rootParser->m_alloc_tracker.bytesAllocated < increase) {
-+    tolerable = false;
-+  } else {
-+    newTotal = rootParser->m_alloc_tracker.bytesAllocated + increase;
-+
-+    if (newTotal >= rootParser->m_alloc_tracker.activationThresholdBytes) {
-+      assert(newTotal > 0);
-+      // NOTE: This can be +infinity when dividing by zero but not -nan
-+      const float amplification
-+          = (float)newTotal / (float)rootParser->m_accounting.countBytesDirect;
-+      if (amplification
-+          > rootParser->m_alloc_tracker.maximumAmplificationFactor) {
-+        tolerable = false;
-+      }
-+    }
-+  }
-+
-+  if (! tolerable && (rootParser->m_alloc_tracker.debugLevel >= 1)) {
-+    expat_heap_stat(rootParser, '+', increase, newTotal, newTotal, sourceLine);
-+  }
-+
-+  return tolerable;
-+}
-+
-+#  if defined(XML_TESTING)
-+void *
-+#  else
-+static void *
-+#  endif
-+expat_malloc(XML_Parser parser, size_t size, int sourceLine) {
-+  // Detect integer overflow
-+  if (SIZE_MAX - size < sizeof(size_t)) {
-+    return NULL;
-+  }
-+
-+  const XML_Parser rootParser = getRootParserOf(parser, NULL);
-+  assert(rootParser->m_parentParser == NULL);
-+
-+  const size_t bytesToAllocate = sizeof(size_t) + size;
-+
-+  if ((XmlBigCount)-1 - rootParser->m_alloc_tracker.bytesAllocated
-+      < bytesToAllocate) {
-+    return NULL; // i.e. signal integer overflow as out-of-memory
-+  }
-+
-+  if (! expat_heap_increase_tolerable(rootParser, bytesToAllocate,
-+                                      sourceLine)) {
-+    return NULL; // i.e. signal violation as out-of-memory
-+  }
-+
-+  // Actually allocate
-+  void *const mallocedPtr = parser->m_mem.malloc_fcn(bytesToAllocate);
-+
-+  if (mallocedPtr == NULL) {
-+    return NULL;
-+  }
-+
-+  // Update in-block recorded size
-+  *(size_t *)mallocedPtr = size;
-+
-+  // Update accounting
-+  rootParser->m_alloc_tracker.bytesAllocated += bytesToAllocate;
-+
-+  // Report as needed
-+  if (rootParser->m_alloc_tracker.debugLevel >= 2) {
-+    if (rootParser->m_alloc_tracker.bytesAllocated
-+        > rootParser->m_alloc_tracker.peakBytesAllocated) {
-+      rootParser->m_alloc_tracker.peakBytesAllocated
-+          = rootParser->m_alloc_tracker.bytesAllocated;
-+    }
-+    expat_heap_stat(rootParser, '+', bytesToAllocate,
-+                    rootParser->m_alloc_tracker.bytesAllocated,
-+                    rootParser->m_alloc_tracker.peakBytesAllocated, sourceLine);
-+  }
-+
-+  return (char *)mallocedPtr + sizeof(size_t);
-+}
-+
-+#  if defined(XML_TESTING)
-+void
-+#  else
-+static void
-+#  endif
-+expat_free(XML_Parser parser, void *ptr, int sourceLine) {
-+  assert(parser != NULL);
-+
-+  if (ptr == NULL) {
-+    return;
-+  }
-+
-+  const XML_Parser rootParser = getRootParserOf(parser, NULL);
-+  assert(rootParser->m_parentParser == NULL);
-+
-+  // Extract size (to the eyes of malloc_fcn/realloc_fcn) and
-+  // the original pointer returned by malloc/realloc
-+  void *const mallocedPtr = (char *)ptr - sizeof(size_t);
-+  const size_t bytesAllocated = sizeof(size_t) + *(size_t *)mallocedPtr;
-+
-+  // Update accounting
-+  assert(rootParser->m_alloc_tracker.bytesAllocated >= bytesAllocated);
-+  rootParser->m_alloc_tracker.bytesAllocated -= bytesAllocated;
-+
-+  // Report as needed
-+  if (rootParser->m_alloc_tracker.debugLevel >= 2) {
-+    expat_heap_stat(rootParser, '-', bytesAllocated,
-+                    rootParser->m_alloc_tracker.bytesAllocated,
-+                    rootParser->m_alloc_tracker.peakBytesAllocated, sourceLine);
-+  }
-+
-+  // NOTE: This may be freeing rootParser, so freeing has to come last
-+  parser->m_mem.free_fcn(mallocedPtr);
-+}
-+
-+#  if defined(XML_TESTING)
-+void *
-+#  else
-+static void *
-+#  endif
-+expat_realloc(XML_Parser parser, void *ptr, size_t size, int sourceLine) {
-+  assert(parser != NULL);
-+
-+  if (ptr == NULL) {
-+    return expat_malloc(parser, size, sourceLine);
-+  }
-+
-+  if (size == 0) {
-+    expat_free(parser, ptr, sourceLine);
-+    return NULL;
-+  }
-+
-+  const XML_Parser rootParser = getRootParserOf(parser, NULL);
-+  assert(rootParser->m_parentParser == NULL);
-+
-+  // Extract original size (to the eyes of the caller) and the original
-+  // pointer returned by malloc/realloc
-+  void *mallocedPtr = (char *)ptr - sizeof(size_t);
-+  const size_t prevSize = *(size_t *)mallocedPtr;
-+
-+  // Classify upcoming change
-+  const bool isIncrease = (size > prevSize);
-+  const size_t absDiff
-+      = (size > prevSize) ? (size - prevSize) : (prevSize - size);
-+
-+  // Ask for permission from accounting
-+  if (isIncrease) {
-+    if (! expat_heap_increase_tolerable(rootParser, absDiff, sourceLine)) {
-+      return NULL; // i.e. signal violation as out-of-memory
-+    }
-+  }
-+
-+  // Actually allocate
-+  mallocedPtr = parser->m_mem.realloc_fcn(mallocedPtr, sizeof(size_t) + size);
-+
-+  if (mallocedPtr == NULL) {
-+    return NULL;
-+  }
-+
-+  // Update accounting
-+  if (isIncrease) {
-+    assert((XmlBigCount)-1 - rootParser->m_alloc_tracker.bytesAllocated
-+           >= absDiff);
-+    rootParser->m_alloc_tracker.bytesAllocated += absDiff;
-+  } else { // i.e. decrease
-+    assert(rootParser->m_alloc_tracker.bytesAllocated >= absDiff);
-+    rootParser->m_alloc_tracker.bytesAllocated -= absDiff;
-+  }
-+
-+  // Report as needed
-+  if (rootParser->m_alloc_tracker.debugLevel >= 2) {
-+    if (rootParser->m_alloc_tracker.bytesAllocated
-+        > rootParser->m_alloc_tracker.peakBytesAllocated) {
-+      rootParser->m_alloc_tracker.peakBytesAllocated
-+          = rootParser->m_alloc_tracker.bytesAllocated;
-+    }
-+    expat_heap_stat(rootParser, isIncrease ? '+' : '-', absDiff,
-+                    rootParser->m_alloc_tracker.bytesAllocated,
-+                    rootParser->m_alloc_tracker.peakBytesAllocated, sourceLine);
-+  }
-+
-+  // Update in-block recorded size
-+  *(size_t *)mallocedPtr = size;
-+
-+  return (char *)mallocedPtr + sizeof(size_t);
-+}
-+#endif // XML_GE == 1
- 
- XML_Parser XMLCALL
- XML_ParserCreate(const XML_Char *encodingName) {
-@@ -1100,19 +1323,40 @@ XML_Parser XMLCALL
- XML_ParserCreate_MM(const XML_Char *encodingName,
-                     const XML_Memory_Handling_Suite *memsuite,
-                     const XML_Char *nameSep) {
--  return parserCreate(encodingName, memsuite, nameSep, NULL);
-+  return parserCreate(encodingName, memsuite, nameSep, NULL, NULL);
- }
- 
- static XML_Parser
- parserCreate(const XML_Char *encodingName,
-              const XML_Memory_Handling_Suite *memsuite, const XML_Char *nameSep,
--             DTD *dtd) {
--  XML_Parser parser;
-+             DTD *dtd, XML_Parser parentParser) {
-+  XML_Parser parser = NULL;
-+
-+#if XML_GE == 1
-+  const size_t increase = sizeof(size_t) + sizeof(struct XML_ParserStruct);
-+
-+  if (parentParser != NULL) {
-+    const XML_Parser rootParser = getRootParserOf(parentParser, NULL);
-+    if (! expat_heap_increase_tolerable(rootParser, increase, __LINE__)) {
-+      return NULL;
-+    }
-+  }
-+#else
-+  UNUSED_P(parentParser);
-+#endif
- 
-   if (memsuite) {
-     XML_Memory_Handling_Suite *mtemp;
-+#if XML_GE == 1
-+    void *const sizeAndParser = memsuite->malloc_fcn(
-+        sizeof(size_t) + sizeof(struct XML_ParserStruct));
-+    if (sizeAndParser != NULL) {
-+      *(size_t *)sizeAndParser = sizeof(struct XML_ParserStruct);
-+      parser = (XML_Parser)((char *)sizeAndParser + sizeof(size_t));
-+#else
-     parser = memsuite->malloc_fcn(sizeof(struct XML_ParserStruct));
-     if (parser != NULL) {
-+#endif
-       mtemp = (XML_Memory_Handling_Suite *)&(parser->m_mem);
-       mtemp->malloc_fcn = memsuite->malloc_fcn;
-       mtemp->realloc_fcn = memsuite->realloc_fcn;
-@@ -1120,18 +1364,67 @@ parserCreate(const XML_Char *encodingName,
-     }
-   } else {
-     XML_Memory_Handling_Suite *mtemp;
-+#if XML_GE == 1
-+    void *const sizeAndParser
-+        = (XML_Parser)malloc(sizeof(size_t) + sizeof(struct XML_ParserStruct));
-+    if (sizeAndParser != NULL) {
-+      *(size_t *)sizeAndParser = sizeof(struct XML_ParserStruct);
-+      parser = (XML_Parser)((char *)sizeAndParser + sizeof(size_t));
-+#else
-     parser = (XML_Parser)malloc(sizeof(struct XML_ParserStruct));
-     if (parser != NULL) {
-+#endif
-       mtemp = (XML_Memory_Handling_Suite *)&(parser->m_mem);
-       mtemp->malloc_fcn = malloc;
-       mtemp->realloc_fcn = realloc;
-       mtemp->free_fcn = free;
-     }
--  }
-+  } // cppcheck-suppress[memleak symbolName=sizeAndParser] // Cppcheck >=2.18.0
- 
-   if (! parser)
-     return parser;
- 
-+#if XML_GE == 1
-+  // Initialize .m_alloc_tracker
-+  memset(&parser->m_alloc_tracker, 0, sizeof(MALLOC_TRACKER));
-+  if (parentParser == NULL) {
-+    parser->m_alloc_tracker.debugLevel
-+        = getDebugLevel("EXPAT_MALLOC_DEBUG", 0u);
-+    parser->m_alloc_tracker.maximumAmplificationFactor
-+        = EXPAT_ALLOC_TRACKER_MAXIMUM_AMPLIFICATION_DEFAULT;
-+    parser->m_alloc_tracker.activationThresholdBytes
-+        = EXPAT_ALLOC_TRACKER_ACTIVATION_THRESHOLD_DEFAULT;
-+
-+    // NOTE: This initialization needs to come this early because these fields
-+    //       are read by allocation tracking code
-+    parser->m_parentParser = NULL;
-+    parser->m_accounting.countBytesDirect = 0;
-+  } else {
-+    parser->m_parentParser = parentParser;
-+  }
-+
-+  // Record XML_ParserStruct allocation we did a few lines up before
-+  const XML_Parser rootParser = getRootParserOf(parser, NULL);
-+  assert(rootParser->m_parentParser == NULL);
-+  assert(SIZE_MAX - rootParser->m_alloc_tracker.bytesAllocated >= increase);
-+  rootParser->m_alloc_tracker.bytesAllocated += increase;
-+
-+  // Report on allocation
-+  if (rootParser->m_alloc_tracker.debugLevel >= 2) {
-+    if (rootParser->m_alloc_tracker.bytesAllocated
-+        > rootParser->m_alloc_tracker.peakBytesAllocated) {
-+      rootParser->m_alloc_tracker.peakBytesAllocated
-+          = rootParser->m_alloc_tracker.bytesAllocated;
-+    }
-+
-+    expat_heap_stat(rootParser, '+', increase,
-+                    rootParser->m_alloc_tracker.bytesAllocated,
-+                    rootParser->m_alloc_tracker.peakBytesAllocated, __LINE__);
-+  }
-+#else
-+  parser->m_parentParser = NULL;
-+#endif // XML_GE == 1
-+
-   parser->m_buffer = NULL;
-   parser->m_bufferLim = NULL;
- 
-@@ -1166,7 +1459,7 @@ parserCreate(const XML_Char *encodingName,
-   if (dtd)
-     parser->m_dtd = dtd;
-   else {
--    parser->m_dtd = dtdCreate(&parser->m_mem);
-+    parser->m_dtd = dtdCreate(parser);
-     if (parser->m_dtd == NULL) {
-       FREE(parser, parser->m_dataBuf);
-       FREE(parser, parser->m_atts);
-@@ -1200,8 +1493,8 @@ parserCreate(const XML_Char *encodingName,
- 
-   parser->m_protocolEncodingName = NULL;
- 
--  poolInit(&parser->m_tempPool, &(parser->m_mem));
--  poolInit(&parser->m_temp2Pool, &(parser->m_mem));
-+  poolInit(&parser->m_tempPool, parser);
-+  poolInit(&parser->m_temp2Pool, parser);
-   parserInit(parser, encodingName);
- 
-   if (encodingName && ! parser->m_protocolEncodingName) {
-@@ -1233,7 +1526,7 @@ parserInit(XML_Parser parser, const XML_Char *encodingName) {
-   parser->m_processor = prologInitProcessor;
-   XmlPrologStateInit(&parser->m_prologState);
-   if (encodingName != NULL) {
--    parser->m_protocolEncodingName = copyString(encodingName, &(parser->m_mem));
-+    parser->m_protocolEncodingName = copyString(encodingName, parser);
-   }
-   parser->m_curBase = NULL;
-   XmlInitEncoding(&parser->m_initEncoding, &parser->m_encoding, 0);
-@@ -1295,7 +1588,6 @@ parserInit(XML_Parser parser, const XML_Char *encodingName) {
-   parser->m_unknownEncodingMem = NULL;
-   parser->m_unknownEncodingRelease = NULL;
-   parser->m_unknownEncodingData = NULL;
--  parser->m_parentParser = NULL;
-   parser->m_parsingStatus.parsing = XML_INITIALIZED;
-   // Reentry can only be triggered inside m_processor calls
-   parser->m_reenter = XML_FALSE;
-@@ -1385,7 +1677,7 @@ XML_ParserReset(XML_Parser parser, const XML_Char *encodingName) {
-   FREE(parser, (void *)parser->m_protocolEncodingName);
-   parser->m_protocolEncodingName = NULL;
-   parserInit(parser, encodingName);
--  dtdReset(parser->m_dtd, &parser->m_mem);
-+  dtdReset(parser->m_dtd, parser);
-   return XML_TRUE;
- }
- 
-@@ -1421,7 +1713,7 @@ XML_SetEncoding(XML_Parser parser, const XML_Char *encodingName) {
-     parser->m_protocolEncodingName = NULL;
-   else {
-     /* Copy the new encoding name into allocated memory */
--    parser->m_protocolEncodingName = copyString(encodingName, &(parser->m_mem));
-+    parser->m_protocolEncodingName = copyString(encodingName, parser);
-     if (! parser->m_protocolEncodingName)
-       return XML_STATUS_ERROR;
-   }
-@@ -1530,9 +1822,10 @@ XML_ExternalEntityParserCreate(XML_Parser oldParser, const XML_Char *context,
-   */
-   if (parser->m_ns) {
-     XML_Char tmp[2] = {parser->m_namespaceSeparator, 0};
--    parser = parserCreate(encodingName, &parser->m_mem, tmp, newDtd);
-+    parser = parserCreate(encodingName, &parser->m_mem, tmp, newDtd, oldParser);
-   } else {
--    parser = parserCreate(encodingName, &parser->m_mem, NULL, newDtd);
-+    parser
-+        = parserCreate(encodingName, &parser->m_mem, NULL, newDtd, oldParser);
-   }
- 
-   if (! parser)
-@@ -1576,7 +1869,7 @@ XML_ExternalEntityParserCreate(XML_Parser oldParser, const XML_Char *context,
-   parser->m_prologState.inEntityValue = oldInEntityValue;
-   if (context) {
- #endif /* XML_DTD */
--    if (! dtdCopy(oldParser, parser->m_dtd, oldDtd, &parser->m_mem)
-+    if (! dtdCopy(oldParser, parser->m_dtd, oldDtd, parser)
-         || ! setContext(parser, context)) {
-       XML_ParserFree(parser);
-       return NULL;
-@@ -1688,14 +1981,16 @@ XML_ParserFree(XML_Parser parser) {
- #else
-   if (parser->m_dtd)
- #endif /* XML_DTD */
--    dtdDestroy(parser->m_dtd, (XML_Bool)! parser->m_parentParser,
--               &parser->m_mem);
-+    dtdDestroy(parser->m_dtd, (XML_Bool)! parser->m_parentParser, parser);
-   FREE(parser, (void *)parser->m_atts);
- #ifdef XML_ATTR_INFO
-   FREE(parser, (void *)parser->m_attInfo);
- #endif
-   FREE(parser, parser->m_groupConnector);
--  FREE(parser, parser->m_buffer);
-+  // NOTE: We are avoiding FREE(..) here because parser->m_buffer
-+  //       is not being allocated with MALLOC(..) but with plain
-+  //       .malloc_fcn(..).
-+  parser->m_mem.free_fcn(parser->m_buffer);
-   FREE(parser, parser->m_dataBuf);
-   FREE(parser, parser->m_nsAtts);
-   FREE(parser, parser->m_unknownEncodingMem);
-@@ -2287,7 +2582,9 @@ XML_GetBuffer(XML_Parser parser, int len) {
-         parser->m_errorCode = XML_ERROR_NO_MEMORY;
-         return NULL;
-       }
--      newBuf = (char *)MALLOC(parser, bufferSize);
-+      // NOTE: We are avoiding MALLOC(..) here to leave limiting
-+      //       the input size to the application using Expat.
-+      newBuf = (char *)parser->m_mem.malloc_fcn(bufferSize);
-       if (newBuf == 0) {
-         parser->m_errorCode = XML_ERROR_NO_MEMORY;
-         return NULL;
-@@ -2298,7 +2595,10 @@ XML_GetBuffer(XML_Parser parser, int len) {
-         memcpy(newBuf, &parser->m_bufferPtr[-keep],
-                EXPAT_SAFE_PTR_DIFF(parser->m_bufferEnd, parser->m_bufferPtr)
-                    + keep);
--        FREE(parser, parser->m_buffer);
-+        // NOTE: We are avoiding FREE(..) here because parser->m_buffer
-+        //       is not being allocated with MALLOC(..) but with plain
-+        //       .malloc_fcn(..).
-+        parser->m_mem.free_fcn(parser->m_buffer);
-         parser->m_buffer = newBuf;
-         parser->m_bufferEnd
-             = parser->m_buffer
-@@ -2314,7 +2614,10 @@ XML_GetBuffer(XML_Parser parser, int len) {
-       if (parser->m_bufferPtr) {
-         memcpy(newBuf, parser->m_bufferPtr,
-                EXPAT_SAFE_PTR_DIFF(parser->m_bufferEnd, parser->m_bufferPtr));
--        FREE(parser, parser->m_buffer);
-+        // NOTE: We are avoiding FREE(..) here because parser->m_buffer
-+        //       is not being allocated with MALLOC(..) but with plain
-+        //       .malloc_fcn(..).
-+        parser->m_mem.free_fcn(parser->m_buffer);
-         parser->m_bufferEnd
-             = newBuf
-               + EXPAT_SAFE_PTR_DIFF(parser->m_bufferEnd, parser->m_bufferPtr);
-@@ -2492,28 +2795,43 @@ XML_GetCurrentColumnNumber(XML_Parser parser) {
- 
- void XMLCALL
- XML_FreeContentModel(XML_Parser parser, XML_Content *model) {
--  if (parser != NULL)
--    FREE(parser, model);
-+  if (parser == NULL)
-+    return;
-+
-+  // NOTE: We are avoiding FREE(..) here because the content model
-+  //       has been created using plain .malloc_fcn(..) rather than MALLOC(..).
-+  parser->m_mem.free_fcn(model);
- }
- 
- void *XMLCALL
- XML_MemMalloc(XML_Parser parser, size_t size) {
-   if (parser == NULL)
-     return NULL;
--  return MALLOC(parser, size);
-+
-+  // NOTE: We are avoiding MALLOC(..) here to not include
-+  //       user allocations with allocation tracking and limiting.
-+  return parser->m_mem.malloc_fcn(size);
- }
- 
- void *XMLCALL
- XML_MemRealloc(XML_Parser parser, void *ptr, size_t size) {
-   if (parser == NULL)
-     return NULL;
--  return REALLOC(parser, ptr, size);
-+
-+  // NOTE: We are avoiding REALLOC(..) here to not include
-+  //       user allocations with allocation tracking and limiting.
-+  return parser->m_mem.realloc_fcn(ptr, size);
- }
- 
- void XMLCALL
- XML_MemFree(XML_Parser parser, void *ptr) {
--  if (parser != NULL)
--    FREE(parser, ptr);
-+  if (parser == NULL)
-+    return;
-+
-+  // NOTE: We are avoiding FREE(..) here because XML_MemMalloc and
-+  //       XML_MemRealloc are not using MALLOC(..) and REALLOC(..)
-+  //       but plain .malloc_fcn(..) and .realloc_fcn(..), internally.
-+  parser->m_mem.free_fcn(ptr);
- }
- 
- void XMLCALL
-@@ -2713,6 +3031,13 @@ XML_GetFeatureList(void) {
-        EXPAT_BILLION_LAUGHS_ATTACK_PROTECTION_ACTIVATION_THRESHOLD_DEFAULT},
-       /* Added in Expat 2.6.0. */
-       {XML_FEATURE_GE, XML_L("XML_GE"), 0},
-+      /* Added in Expat 2.7.2. */
-+      {XML_FEATURE_ALLOC_TRACKER_MAXIMUM_AMPLIFICATION_DEFAULT,
-+       XML_L("XML_AT_MAX_AMP"),
-+       (long int)EXPAT_ALLOC_TRACKER_MAXIMUM_AMPLIFICATION_DEFAULT},
-+      {XML_FEATURE_ALLOC_TRACKER_ACTIVATION_THRESHOLD_DEFAULT,
-+       XML_L("XML_AT_ACT_THRES"),
-+       (long int)EXPAT_ALLOC_TRACKER_ACTIVATION_THRESHOLD_DEFAULT},
- #endif
-       {XML_FEATURE_END, NULL, 0}};
- 
-@@ -2741,6 +3066,29 @@ XML_SetBillionLaughsAttackProtectionActivationThreshold(
-   parser->m_accounting.activationThresholdBytes = activationThresholdBytes;
-   return XML_TRUE;
- }
-+
-+XML_Bool XMLCALL
-+XML_SetAllocTrackerMaximumAmplification(XML_Parser parser,
-+                                        float maximumAmplificationFactor) {
-+  if ((parser == NULL) || (parser->m_parentParser != NULL)
-+      || isnan(maximumAmplificationFactor)
-+      || (maximumAmplificationFactor < 1.0f)) {
-+    return XML_FALSE;
-+  }
-+  parser->m_alloc_tracker.maximumAmplificationFactor
-+      = maximumAmplificationFactor;
-+  return XML_TRUE;
-+}
-+
-+XML_Bool XMLCALL
-+XML_SetAllocTrackerActivationThreshold(
-+    XML_Parser parser, unsigned long long activationThresholdBytes) {
-+  if ((parser == NULL) || (parser->m_parentParser != NULL)) {
-+    return XML_FALSE;
-+  }
-+  parser->m_alloc_tracker.activationThresholdBytes = activationThresholdBytes;
-+  return XML_TRUE;
-+}
- #endif /* XML_GE == 1 */
- 
- XML_Bool XMLCALL
-@@ -5726,8 +6074,12 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end,
-     case XML_ROLE_CONTENT_EMPTY:
-       if (dtd->in_eldecl) {
-         if (parser->m_elementDeclHandler) {
-+          // NOTE: We are avoiding MALLOC(..) here to so that
-+          //       applications that are not using XML_FreeContentModel but
-+          //       plain free(..) or .free_fcn() to free the content model's
-+          //       memory are safe.
-           XML_Content *content
--              = (XML_Content *)MALLOC(parser, sizeof(XML_Content));
-+              = (XML_Content *)parser->m_mem.malloc_fcn(sizeof(XML_Content));
-           if (! content)
-             return XML_ERROR_NO_MEMORY;
-           content->quant = XML_CQUANT_NONE;
-@@ -7116,19 +7468,19 @@ normalizePublicId(XML_Char *publicId) {
- }
- 
- static DTD *
--dtdCreate(const XML_Memory_Handling_Suite *ms) {
--  DTD *p = ms->malloc_fcn(sizeof(DTD));
-+dtdCreate(XML_Parser parser) {
-+  DTD *p = MALLOC(parser, sizeof(DTD));
-   if (p == NULL)
-     return p;
--  poolInit(&(p->pool), ms);
--  poolInit(&(p->entityValuePool), ms);
--  hashTableInit(&(p->generalEntities), ms);
--  hashTableInit(&(p->elementTypes), ms);
--  hashTableInit(&(p->attributeIds), ms);
--  hashTableInit(&(p->prefixes), ms);
-+  poolInit(&(p->pool), parser);
-+  poolInit(&(p->entityValuePool), parser);
-+  hashTableInit(&(p->generalEntities), parser);
-+  hashTableInit(&(p->elementTypes), parser);
-+  hashTableInit(&(p->attributeIds), parser);
-+  hashTableInit(&(p->prefixes), parser);
- #ifdef XML_DTD
-   p->paramEntityRead = XML_FALSE;
--  hashTableInit(&(p->paramEntities), ms);
-+  hashTableInit(&(p->paramEntities), parser);
- #endif /* XML_DTD */
-   p->defaultPrefix.name = NULL;
-   p->defaultPrefix.binding = NULL;
-@@ -7148,7 +7500,7 @@ dtdCreate(const XML_Memory_Handling_Suite *ms) {
- }
- 
- static void
--dtdReset(DTD *p, const XML_Memory_Handling_Suite *ms) {
-+dtdReset(DTD *p, XML_Parser parser) {
-   HASH_TABLE_ITER iter;
-   hashTableIterInit(&iter, &(p->elementTypes));
-   for (;;) {
-@@ -7156,7 +7508,7 @@ dtdReset(DTD *p, const XML_Memory_Handling_Suite *ms) {
-     if (! e)
-       break;
-     if (e->allocDefaultAtts != 0)
--      ms->free_fcn(e->defaultAtts);
-+      FREE(parser, e->defaultAtts);
-   }
-   hashTableClear(&(p->generalEntities));
- #ifdef XML_DTD
-@@ -7173,9 +7525,9 @@ dtdReset(DTD *p, const XML_Memory_Handling_Suite *ms) {
- 
-   p->in_eldecl = XML_FALSE;
- 
--  ms->free_fcn(p->scaffIndex);
-+  FREE(parser, p->scaffIndex);
-   p->scaffIndex = NULL;
--  ms->free_fcn(p->scaffold);
-+  FREE(parser, p->scaffold);
-   p->scaffold = NULL;
- 
-   p->scaffLevel = 0;
-@@ -7189,7 +7541,7 @@ dtdReset(DTD *p, const XML_Memory_Handling_Suite *ms) {
- }
- 
- static void
--dtdDestroy(DTD *p, XML_Bool isDocEntity, const XML_Memory_Handling_Suite *ms) {
-+dtdDestroy(DTD *p, XML_Bool isDocEntity, XML_Parser parser) {
-   HASH_TABLE_ITER iter;
-   hashTableIterInit(&iter, &(p->elementTypes));
-   for (;;) {
-@@ -7197,7 +7549,7 @@ dtdDestroy(DTD *p, XML_Bool isDocEntity, const XML_Memory_Handling_Suite *ms) {
-     if (! e)
-       break;
-     if (e->allocDefaultAtts != 0)
--      ms->free_fcn(e->defaultAtts);
-+      FREE(parser, e->defaultAtts);
-   }
-   hashTableDestroy(&(p->generalEntities));
- #ifdef XML_DTD
-@@ -7209,10 +7561,10 @@ dtdDestroy(DTD *p, XML_Bool isDocEntity, const XML_Memory_Handling_Suite *ms) {
-   poolDestroy(&(p->pool));
-   poolDestroy(&(p->entityValuePool));
-   if (isDocEntity) {
--    ms->free_fcn(p->scaffIndex);
--    ms->free_fcn(p->scaffold);
-+    FREE(parser, p->scaffIndex);
-+    FREE(parser, p->scaffold);
-   }
--  ms->free_fcn(p);
-+  FREE(parser, p);
- }
- 
- /* Do a deep copy of the DTD. Return 0 for out of memory, non-zero otherwise.
-@@ -7220,7 +7572,7 @@ dtdDestroy(DTD *p, XML_Bool isDocEntity, const XML_Memory_Handling_Suite *ms) {
- */
- static int
- dtdCopy(XML_Parser oldParser, DTD *newDtd, const DTD *oldDtd,
--        const XML_Memory_Handling_Suite *ms) {
-+        XML_Parser parser) {
-   HASH_TABLE_ITER iter;
- 
-   /* Copy the prefix table. */
-@@ -7301,7 +7653,7 @@ dtdCopy(XML_Parser oldParser, DTD *newDtd, const DTD *oldDtd,
-       }
- #endif
-       newE->defaultAtts
--          = ms->malloc_fcn(oldE->nDefaultAtts * sizeof(DEFAULT_ATTRIBUTE));
-+          = MALLOC(parser, oldE->nDefaultAtts * sizeof(DEFAULT_ATTRIBUTE));
-       if (! newE->defaultAtts) {
-         return 0;
-       }
-@@ -7463,7 +7815,7 @@ lookup(XML_Parser parser, HASH_TABLE *table, KEY name, size_t createSize) {
-     /* table->size is a power of 2 */
-     table->size = (size_t)1 << INIT_POWER;
-     tsize = table->size * sizeof(NAMED *);
--    table->v = table->mem->malloc_fcn(tsize);
-+    table->v = MALLOC(table->parser, tsize);
-     if (! table->v) {
-       table->size = 0;
-       return NULL;
-@@ -7503,7 +7855,7 @@ lookup(XML_Parser parser, HASH_TABLE *table, KEY name, size_t createSize) {
-       }
- 
-       size_t tsize = newSize * sizeof(NAMED *);
--      NAMED **newV = table->mem->malloc_fcn(tsize);
-+      NAMED **newV = MALLOC(table->parser, tsize);
-       if (! newV)
-         return NULL;
-       memset(newV, 0, tsize);
-@@ -7519,7 +7871,7 @@ lookup(XML_Parser parser, HASH_TABLE *table, KEY name, size_t createSize) {
-           }
-           newV[j] = table->v[i];
-         }
--      table->mem->free_fcn(table->v);
-+      FREE(table->parser, table->v);
-       table->v = newV;
-       table->power = newPower;
-       table->size = newSize;
-@@ -7532,7 +7884,7 @@ lookup(XML_Parser parser, HASH_TABLE *table, KEY name, size_t createSize) {
-       }
-     }
-   }
--  table->v[i] = table->mem->malloc_fcn(createSize);
-+  table->v[i] = MALLOC(table->parser, createSize);
-   if (! table->v[i])
-     return NULL;
-   memset(table->v[i], 0, createSize);
-@@ -7545,7 +7897,7 @@ static void FASTCALL
- hashTableClear(HASH_TABLE *table) {
-   size_t i;
-   for (i = 0; i < table->size; i++) {
--    table->mem->free_fcn(table->v[i]);
-+    FREE(table->parser, table->v[i]);
-     table->v[i] = NULL;
-   }
-   table->used = 0;
-@@ -7555,17 +7907,17 @@ static void FASTCALL
- hashTableDestroy(HASH_TABLE *table) {
-   size_t i;
-   for (i = 0; i < table->size; i++)
--    table->mem->free_fcn(table->v[i]);
--  table->mem->free_fcn(table->v);
-+    FREE(table->parser, table->v[i]);
-+  FREE(table->parser, table->v);
- }
- 
- static void FASTCALL
--hashTableInit(HASH_TABLE *p, const XML_Memory_Handling_Suite *ms) {
-+hashTableInit(HASH_TABLE *p, XML_Parser parser) {
-   p->power = 0;
-   p->size = 0;
-   p->used = 0;
-   p->v = NULL;
--  p->mem = ms;
-+  p->parser = parser;
- }
- 
- static void FASTCALL
-@@ -7585,13 +7937,13 @@ hashTableIterNext(HASH_TABLE_ITER *iter) {
- }
- 
- static void FASTCALL
--poolInit(STRING_POOL *pool, const XML_Memory_Handling_Suite *ms) {
-+poolInit(STRING_POOL *pool, XML_Parser parser) {
-   pool->blocks = NULL;
-   pool->freeBlocks = NULL;
-   pool->start = NULL;
-   pool->ptr = NULL;
-   pool->end = NULL;
--  pool->mem = ms;
-+  pool->parser = parser;
- }
- 
- static void FASTCALL
-@@ -7618,13 +7970,13 @@ poolDestroy(STRING_POOL *pool) {
-   BLOCK *p = pool->blocks;
-   while (p) {
-     BLOCK *tem = p->next;
--    pool->mem->free_fcn(p);
-+    FREE(pool->parser, p);
-     p = tem;
-   }
-   p = pool->freeBlocks;
-   while (p) {
-     BLOCK *tem = p->next;
--    pool->mem->free_fcn(p);
-+    FREE(pool->parser, p);
-     p = tem;
-   }
- }
-@@ -7779,8 +8131,8 @@ poolGrow(STRING_POOL *pool) {
-     if (bytesToAllocate == 0)
-       return XML_FALSE;
- 
--    temp = (BLOCK *)pool->mem->realloc_fcn(pool->blocks,
--                                           (unsigned)bytesToAllocate);
-+    temp = (BLOCK *)REALLOC(pool->parser, pool->blocks,
-+                            (unsigned)bytesToAllocate);
-     if (temp == NULL)
-       return XML_FALSE;
-     pool->blocks = temp;
-@@ -7820,7 +8172,7 @@ poolGrow(STRING_POOL *pool) {
-     if (bytesToAllocate == 0)
-       return XML_FALSE;
- 
--    tem = pool->mem->malloc_fcn(bytesToAllocate);
-+    tem = MALLOC(pool->parser, bytesToAllocate);
-     if (! tem)
-       return XML_FALSE;
-     tem->size = blockSize;
-@@ -7935,7 +8287,10 @@ build_model(XML_Parser parser) {
-   const size_t allocsize = (dtd->scaffCount * sizeof(XML_Content)
-                             + (dtd->contentStringLen * sizeof(XML_Char)));
- 
--  ret = (XML_Content *)MALLOC(parser, allocsize);
-+  // NOTE: We are avoiding MALLOC(..) here to so that
-+  //       applications that are not using XML_FreeContentModel but plain
-+  //       free(..) or .free_fcn() to free the content model's memory are safe.
-+  ret = (XML_Content *)parser->m_mem.malloc_fcn(allocsize);
-   if (! ret)
-     return NULL;
- 
-@@ -8056,7 +8411,7 @@ getElementType(XML_Parser parser, const ENCODING *enc, const char *ptr,
- }
- 
- static XML_Char *
--copyString(const XML_Char *s, const XML_Memory_Handling_Suite *memsuite) {
-+copyString(const XML_Char *s, XML_Parser parser) {
-   size_t charsRequired = 0;
-   XML_Char *result;
- 
-@@ -8068,7 +8423,7 @@ copyString(const XML_Char *s, const XML_Memory_Handling_Suite *memsuite) {
-   charsRequired++;
- 
-   /* Now allocate space for the copy */
--  result = memsuite->malloc_fcn(charsRequired * sizeof(XML_Char));
-+  result = MALLOC(parser, charsRequired * sizeof(XML_Char));
-   if (result == NULL)
-     return NULL;
-   /* Copy the original into place */
-diff --git a/tests/alloc_tests.c b/tests/alloc_tests.c
-index 12ea3b2..47004a9 100644
---- a/tests/alloc_tests.c
-+++ b/tests/alloc_tests.c
-@@ -46,10 +46,16 @@
- #  undef NDEBUG /* because test suite relies on assert(...) at the moment */
- #endif
- 
-+#include  /* NAN, INFINITY */
-+#include 
-+#include  /* for SIZE_MAX */
- #include 
- #include 
- 
-+#include "expat_config.h"
-+
- #include "expat.h"
-+#include "internal.h"
- #include "common.h"
- #include "minicheck.h"
- #include "dummy.h"
-@@ -2085,6 +2091,203 @@ START_TEST(test_alloc_reset_after_external_entity_parser_create_fail) {
- }
- END_TEST
- 
-+START_TEST(test_alloc_tracker_size_recorded) {
-+  XML_Memory_Handling_Suite memsuite = {malloc, realloc, free};
-+
-+  bool values[] = {true, false};
-+  for (size_t i = 0; i < sizeof(values) / sizeof(values[0]); i++) {
-+    const bool useMemSuite = values[i];
-+    set_subtest("useMemSuite=%d", (int)useMemSuite);
-+    XML_Parser parser = useMemSuite
-+                            ? XML_ParserCreate_MM(NULL, &memsuite, XCS("|"))
-+                            : XML_ParserCreate(NULL);
-+
-+#if XML_GE == 1
-+    void *ptr = expat_malloc(parser, 10, -1);
-+
-+    assert_true(ptr != NULL);
-+    assert_true(*((size_t *)ptr - 1) == 10);
-+
-+    assert_true(expat_realloc(parser, ptr, SIZE_MAX / 2, -1) == NULL);
-+
-+    assert_true(*((size_t *)ptr - 1) == 10); // i.e. unchanged
-+
-+    ptr = expat_realloc(parser, ptr, 20, -1);
-+
-+    assert_true(ptr != NULL);
-+    assert_true(*((size_t *)ptr - 1) == 20);
-+
-+    expat_free(parser, ptr, -1);
-+#endif
-+
-+    XML_ParserFree(parser);
-+  }
-+}
-+END_TEST
-+
-+START_TEST(test_alloc_tracker_maximum_amplification) {
-+  if (g_reparseDeferralEnabledDefault == XML_TRUE) {
-+    return;
-+  }
-+
-+  XML_Parser parser = XML_ParserCreate(NULL);
-+
-+  // Get .m_accounting.countBytesDirect from 0 to 3
-+  const char *const chunk = "";
-+  assert_true(_XML_Parse_SINGLE_BYTES(parser, chunk, (int)strlen(chunk),
-+                                      /*isFinal=*/XML_FALSE)
-+              == XML_STATUS_OK);
-+
-+#if XML_GE == 1
-+  // Stop activation threshold from interfering
-+  assert_true(XML_SetAllocTrackerActivationThreshold(parser, 0) == XML_TRUE);
-+
-+  // Exceed maximum amplification: should be rejected.
-+  assert_true(expat_malloc(parser, 1000, -1) == NULL);
-+
-+  // Increase maximum amplification, and try the same amount once more: should
-+  // work.
-+  assert_true(XML_SetAllocTrackerMaximumAmplification(parser, 3000.0f)
-+              == XML_TRUE);
-+
-+  void *const ptr = expat_malloc(parser, 1000, -1);
-+  assert_true(ptr != NULL);
-+  expat_free(parser, ptr, -1);
-+#endif
-+
-+  XML_ParserFree(parser);
-+}
-+END_TEST
-+
-+START_TEST(test_alloc_tracker_threshold) {
-+  XML_Parser parser = XML_ParserCreate(NULL);
-+
-+#if XML_GE == 1
-+  // Exceed maximum amplification *before* (default) threshold: should work.
-+  void *const ptr = expat_malloc(parser, 1000, -1);
-+  assert_true(ptr != NULL);
-+  expat_free(parser, ptr, -1);
-+
-+  // Exceed maximum amplification *after* threshold: should be rejected.
-+  assert_true(XML_SetAllocTrackerActivationThreshold(parser, 999) == XML_TRUE);
-+  assert_true(expat_malloc(parser, 1000, -1) == NULL);
-+#endif
-+
-+  XML_ParserFree(parser);
-+}
-+END_TEST
-+
-+START_TEST(test_alloc_tracker_getbuffer_unlimited) {
-+  XML_Parser parser = XML_ParserCreate(NULL);
-+
-+#if XML_GE == 1
-+  // Artificially lower threshold
-+  assert_true(XML_SetAllocTrackerActivationThreshold(parser, 0) == XML_TRUE);
-+
-+  // Self-test: Prove that threshold is as rejecting as expected
-+  assert_true(expat_malloc(parser, 1000, -1) == NULL);
-+#endif
-+  // XML_GetBuffer should be allowed to pass, though
-+  assert_true(XML_GetBuffer(parser, 1000) != NULL);
-+
-+  XML_ParserFree(parser);
-+}
-+END_TEST
-+
-+START_TEST(test_alloc_tracker_api) {
-+  XML_Parser parserWithoutParent = XML_ParserCreate(NULL);
-+  XML_Parser parserWithParent = XML_ExternalEntityParserCreate(
-+      parserWithoutParent, XCS("entity123"), NULL);
-+  if (parserWithoutParent == NULL)
-+    fail("parserWithoutParent is NULL");
-+  if (parserWithParent == NULL)
-+    fail("parserWithParent is NULL");
-+
-+#if XML_GE == 1
-+  // XML_SetAllocTrackerMaximumAmplification, error cases
-+  if (XML_SetAllocTrackerMaximumAmplification(NULL, 123.0f) == XML_TRUE)
-+    fail("Call with NULL parser is NOT supposed to succeed");
-+  if (XML_SetAllocTrackerMaximumAmplification(parserWithParent, 123.0f)
-+      == XML_TRUE)
-+    fail("Call with non-root parser is NOT supposed to succeed");
-+  if (XML_SetAllocTrackerMaximumAmplification(parserWithoutParent, NAN)
-+      == XML_TRUE)
-+    fail("Call with NaN limit is NOT supposed to succeed");
-+  if (XML_SetAllocTrackerMaximumAmplification(parserWithoutParent, -1.0f)
-+      == XML_TRUE)
-+    fail("Call with negative limit is NOT supposed to succeed");
-+  if (XML_SetAllocTrackerMaximumAmplification(parserWithoutParent, 0.9f)
-+      == XML_TRUE)
-+    fail("Call with positive limit <1.0 is NOT supposed to succeed");
-+
-+  // XML_SetAllocTrackerMaximumAmplification, success cases
-+  if (XML_SetAllocTrackerMaximumAmplification(parserWithoutParent, 1.0f)
-+      == XML_FALSE)
-+    fail("Call with positive limit >=1.0 is supposed to succeed");
-+  if (XML_SetAllocTrackerMaximumAmplification(parserWithoutParent, 123456.789f)
-+      == XML_FALSE)
-+    fail("Call with positive limit >=1.0 is supposed to succeed");
-+  if (XML_SetAllocTrackerMaximumAmplification(parserWithoutParent, INFINITY)
-+      == XML_FALSE)
-+    fail("Call with positive limit >=1.0 is supposed to succeed");
-+
-+  // XML_SetAllocTrackerActivationThreshold, error cases
-+  if (XML_SetAllocTrackerActivationThreshold(NULL, 123) == XML_TRUE)
-+    fail("Call with NULL parser is NOT supposed to succeed");
-+  if (XML_SetAllocTrackerActivationThreshold(parserWithParent, 123) == XML_TRUE)
-+    fail("Call with non-root parser is NOT supposed to succeed");
-+
-+  // XML_SetAllocTrackerActivationThreshold, success cases
-+  if (XML_SetAllocTrackerActivationThreshold(parserWithoutParent, 123)
-+      == XML_FALSE)
-+    fail("Call with non-NULL parentless parser is supposed to succeed");
-+#endif // XML_GE == 1
-+
-+  XML_ParserFree(parserWithParent);
-+  XML_ParserFree(parserWithoutParent);
-+}
-+END_TEST
-+
-+START_TEST(test_mem_api_cycle) {
-+  XML_Parser parser = XML_ParserCreate(NULL);
-+
-+  void *ptr = XML_MemMalloc(parser, 10);
-+
-+  assert_true(ptr != NULL);
-+  memset(ptr, 'x', 10); // assert writability, with ASan in mind
-+
-+  ptr = XML_MemRealloc(parser, ptr, 20);
-+
-+  assert_true(ptr != NULL);
-+  memset(ptr, 'y', 20); // assert writability, with ASan in mind
-+
-+  XML_MemFree(parser, ptr);
-+
-+  XML_ParserFree(parser);
-+}
-+END_TEST
-+
-+START_TEST(test_mem_api_unlimited) {
-+  XML_Parser parser = XML_ParserCreate(NULL);
-+
-+#if XML_GE == 1
-+  assert_true(XML_SetAllocTrackerActivationThreshold(parser, 0) == XML_TRUE);
-+#endif
-+
-+  void *ptr = XML_MemMalloc(parser, 1000);
-+
-+  assert_true(ptr != NULL);
-+
-+  ptr = XML_MemRealloc(parser, ptr, 2000);
-+
-+  assert_true(ptr != NULL);
-+
-+  XML_MemFree(parser, ptr);
-+
-+  XML_ParserFree(parser);
-+}
-+END_TEST
-+
- void
- make_alloc_test_case(Suite *s) {
-   TCase *tc_alloc = tcase_create("allocation tests");
-@@ -2151,4 +2354,15 @@ make_alloc_test_case(Suite *s) {
- 
-   tcase_add_test__ifdef_xml_dtd(
-       tc_alloc, test_alloc_reset_after_external_entity_parser_create_fail);
-+
-+  tcase_add_test__ifdef_xml_dtd(tc_alloc, test_alloc_tracker_size_recorded);
-+  tcase_add_test__ifdef_xml_dtd(tc_alloc,
-+                                test_alloc_tracker_maximum_amplification);
-+  tcase_add_test__ifdef_xml_dtd(tc_alloc, test_alloc_tracker_threshold);
-+  tcase_add_test__ifdef_xml_dtd(tc_alloc,
-+                                test_alloc_tracker_getbuffer_unlimited);
-+  tcase_add_test__ifdef_xml_dtd(tc_alloc, test_alloc_tracker_api);
-+
-+  tcase_add_test(tc_alloc, test_mem_api_cycle);
-+  tcase_add_test__ifdef_xml_dtd(tc_alloc, test_mem_api_unlimited);
- }
-diff --git a/tests/basic_tests.c b/tests/basic_tests.c
-index f0025fc..da5c0d4 100644
---- a/tests/basic_tests.c
-+++ b/tests/basic_tests.c
-@@ -3002,6 +3002,10 @@ START_TEST(test_buffer_can_grow_to_max) {
-   for (int i = 0; i < num_prefixes; ++i) {
-     set_subtest("\"%s\"", prefixes[i]);
-     XML_Parser parser = XML_ParserCreate(NULL);
-+#if XML_GE == 1
-+    assert_true(XML_SetAllocTrackerActivationThreshold(parser, (size_t)-1)
-+                == XML_TRUE); // i.e. deactivate
-+#endif
-     const int prefix_len = (int)strlen(prefixes[i]);
-     const enum XML_Status s
-         = _XML_Parse_SINGLE_BYTES(parser, prefixes[i], prefix_len, XML_FALSE);
-diff --git a/tests/nsalloc_tests.c b/tests/nsalloc_tests.c
-index ec88586..a8f5718 100644
---- a/tests/nsalloc_tests.c
-+++ b/tests/nsalloc_tests.c
-@@ -454,10 +454,15 @@ START_TEST(test_nsalloc_realloc_attributes) {
-     nsalloc_teardown();
-     nsalloc_setup();
-   }
-+#if XML_GE == 1
-+  assert_true(
-+      i == 0); // because expat_realloc relies on expat_malloc to some extent
-+#else
-   if (i == 0)
-     fail("Parsing worked despite failing reallocations");
-   else if (i == max_realloc_count)
-     fail("Parsing failed at max reallocation count");
-+#endif
- }
- END_TEST
- 
-diff --git a/xmlwf/xmlwf.c b/xmlwf/xmlwf.c
-index 7c0a8cd..92adb1a 100644
---- a/xmlwf/xmlwf.c
-+++ b/xmlwf/xmlwf.c
-@@ -913,11 +913,11 @@ usage(const XML_Char *prog, int rc) {
-       T("  -t             write no XML output for [t]iming of plain parsing\n")
-       T("  -N             enable adding doctype and [n]otation declarations\n")
-       T("\n")
--      T("billion laughs attack protection:\n")
-+      T("amplification attack protection (e.g. billion laughs):\n")
-       T("  NOTE: If you ever need to increase these values for non-attack payload, please file a bug report.\n")
-       T("\n")
-       T("  -a FACTOR      set maximum tolerated [a]mplification factor (default: 100.0)\n")
--      T("  -b BYTES       set number of output [b]ytes needed to activate (default: 8 MiB)\n")
-+      T("  -b BYTES       set number of output [b]ytes needed to activate (default: 8 MiB/64 MiB)\n")
-       T("\n")
-       T("reparse deferral:\n")
-       T("  -q             disable reparse deferral, and allow [q]uadratic parse runtime with large tokens\n")
-@@ -926,6 +926,10 @@ usage(const XML_Char *prog, int rc) {
-       T("  -h, --help     show this [h]elp message and exit\n")
-       T("  -v, --version  show program's [v]ersion number and exit\n")
-       T("\n")
-+      T("environment variables:\n")
-+      T("  EXPAT_MALLOC_DEBUG=(0|1|2)\n")
-+      T("                 Control verbosity of allocation tracker (default: 0)\n")
-+      T("\n")
-       T("exit status:\n")
-       T("  0              the input files are well-formed and the output (if requested) was written successfully\n")
-       T("  1              could not allocate data structures, signals a serious problem with execution environment\n")
-@@ -1171,12 +1175,15 @@ tmain(int argc, XML_Char **argv) {
- #if XML_GE == 1
-       XML_SetBillionLaughsAttackProtectionMaximumAmplification(
-           parser, attackMaximumAmplification);
-+      XML_SetAllocTrackerMaximumAmplification(parser,
-+                                              attackMaximumAmplification);
- #endif
-     }
-     if (attackThresholdGiven) {
- #if XML_GE == 1
-       XML_SetBillionLaughsAttackProtectionActivationThreshold(
-           parser, attackThresholdBytes);
-+      XML_SetAllocTrackerActivationThreshold(parser, attackThresholdBytes);
- #else
-       (void)attackThresholdBytes; // silence -Wunused-but-set-variable
- #endif
-diff --git a/xmlwf/xmlwf_helpgen.py b/xmlwf/xmlwf_helpgen.py
-index 3d32f5d..dcae018 100755
---- a/xmlwf/xmlwf_helpgen.py
-+++ b/xmlwf/xmlwf_helpgen.py
-@@ -32,6 +32,9 @@
- import argparse
- 
- epilog = """
-+environment variables:
-+  EXPAT_MALLOC_DEBUG=(0|1|2)
-+                 Control verbosity of allocation tracker (default: 0)
- exit status:
-   0              the input files are well-formed and the output (if requested) was written successfully
-   1              could not allocate data structures, signals a serious problem with execution environment
--- 
-2.43.0
-
diff --git a/SPECS/expat/CVE-2026-24515.patch b/SPECS/expat/CVE-2026-24515.patch
deleted file mode 100644
index f83bc89770a..00000000000
--- a/SPECS/expat/CVE-2026-24515.patch
+++ /dev/null
@@ -1,169 +0,0 @@
-From 8b3805af3dde1f5424bdd704da688def3a81ff9f Mon Sep 17 00:00:00 2001
-From: Sebastian Pipping 
-Date: Sun, 18 Jan 2026 17:53:37 +0100
-Subject: [PATCH 1/3] lib: Make XML_ExternalEntityParserCreate copy unknown
- encoding handler user data
-
-Patch suggested by Artiphishell Inc.
----
- lib/xmlparse.c | 3 +++
- 1 file changed, 3 insertions(+)
-
-diff --git a/lib/xmlparse.c b/lib/xmlparse.c
-index e2847b1..d804753 100644
---- a/lib/xmlparse.c
-+++ b/lib/xmlparse.c
-@@ -1742,6 +1742,7 @@ XML_ExternalEntityParserCreate(XML_Parser oldParser, const XML_Char *context,
-   XML_ExternalEntityRefHandler oldExternalEntityRefHandler;
-   XML_SkippedEntityHandler oldSkippedEntityHandler;
-   XML_UnknownEncodingHandler oldUnknownEncodingHandler;
-+  void *oldUnknownEncodingHandlerData;
-   XML_ElementDeclHandler oldElementDeclHandler;
-   XML_AttlistDeclHandler oldAttlistDeclHandler;
-   XML_EntityDeclHandler oldEntityDeclHandler;
-@@ -1787,6 +1788,7 @@ XML_ExternalEntityParserCreate(XML_Parser oldParser, const XML_Char *context,
-   oldExternalEntityRefHandler = parser->m_externalEntityRefHandler;
-   oldSkippedEntityHandler = parser->m_skippedEntityHandler;
-   oldUnknownEncodingHandler = parser->m_unknownEncodingHandler;
-+  oldUnknownEncodingHandlerData = parser->m_unknownEncodingHandlerData;
-   oldElementDeclHandler = parser->m_elementDeclHandler;
-   oldAttlistDeclHandler = parser->m_attlistDeclHandler;
-   oldEntityDeclHandler = parser->m_entityDeclHandler;
-@@ -1847,6 +1849,7 @@ XML_ExternalEntityParserCreate(XML_Parser oldParser, const XML_Char *context,
-   parser->m_externalEntityRefHandler = oldExternalEntityRefHandler;
-   parser->m_skippedEntityHandler = oldSkippedEntityHandler;
-   parser->m_unknownEncodingHandler = oldUnknownEncodingHandler;
-+  parser->m_unknownEncodingHandlerData = oldUnknownEncodingHandlerData;
-   parser->m_elementDeclHandler = oldElementDeclHandler;
-   parser->m_attlistDeclHandler = oldAttlistDeclHandler;
-   parser->m_entityDeclHandler = oldEntityDeclHandler;
--- 
-2.45.4
-
-
-From 0b2599c6eff2f0f9d20ec7423621f2928f989c1a Mon Sep 17 00:00:00 2001
-From: Sebastian Pipping 
-Date: Sun, 18 Jan 2026 17:26:31 +0100
-Subject: [PATCH 2/3] tests: Cover effect of XML_SetUnknownEncodingHandler user
- data
-
----
- tests/basic_tests.c | 42 ++++++++++++++++++++++++++++++++++++++++++
- tests/handlers.c    | 10 ++++++++++
- tests/handlers.h    |  3 +++
- 3 files changed, 55 insertions(+)
-
-diff --git a/tests/basic_tests.c b/tests/basic_tests.c
-index da5c0d4..2db2a76 100644
---- a/tests/basic_tests.c
-+++ b/tests/basic_tests.c
-@@ -4440,6 +4440,46 @@ START_TEST(test_unknown_encoding_invalid_attr_value) {
- }
- END_TEST
- 
-+START_TEST(test_unknown_encoding_user_data_primary) {
-+  // This test is based on ideas contributed by Artiphishell Inc.
-+  const char *const text = "\n"
-+                           "\n";
-+  XML_Parser parser = XML_ParserCreate(NULL);
-+  XML_SetUnknownEncodingHandler(parser,
-+                                user_data_checking_unknown_encoding_handler,
-+                                (void *)(intptr_t)0xC0FFEE);
-+
-+  assert_true(_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE)
-+              == XML_STATUS_OK);
-+
-+  XML_ParserFree(parser);
-+}
-+END_TEST
-+
-+START_TEST(test_unknown_encoding_user_data_secondary) {
-+  // This test is based on ideas contributed by Artiphishell Inc.
-+  const char *const text_main = "\n"
-+                                "]>\n"
-+                                "&ext;\n";
-+  const char *const text_external = "\n"
-+                                    "data";
-+  ExtTest2 test_data = {text_external, (int)strlen(text_external), NULL, NULL};
-+  XML_Parser parser = XML_ParserCreate(NULL);
-+  XML_SetExternalEntityRefHandler(parser, external_entity_loader2);
-+  XML_SetUnknownEncodingHandler(parser,
-+                                user_data_checking_unknown_encoding_handler,
-+                                (void *)(intptr_t)0xC0FFEE);
-+  XML_SetUserData(parser, &test_data);
-+
-+  assert_true(_XML_Parse_SINGLE_BYTES(parser, text_main, (int)strlen(text_main),
-+                                      XML_TRUE)
-+              == XML_STATUS_OK);
-+
-+  XML_ParserFree(parser);
-+}
-+END_TEST
-+
- /* Test an external entity parser set to use latin-1 detects UTF-16
-  * BOMs correctly.
-  */
-@@ -6284,6 +6324,8 @@ make_basic_test_case(Suite *s) {
-   tcase_add_test(tc_basic, test_unknown_encoding_invalid_surrogate);
-   tcase_add_test(tc_basic, test_unknown_encoding_invalid_high);
-   tcase_add_test(tc_basic, test_unknown_encoding_invalid_attr_value);
-+  tcase_add_test(tc_basic, test_unknown_encoding_user_data_primary);
-+  tcase_add_test(tc_basic, test_unknown_encoding_user_data_secondary);
-   tcase_add_test__if_xml_ge(tc_basic, test_ext_entity_latin1_utf16le_bom);
-   tcase_add_test__if_xml_ge(tc_basic, test_ext_entity_latin1_utf16be_bom);
-   tcase_add_test__if_xml_ge(tc_basic, test_ext_entity_latin1_utf16le_bom2);
-diff --git a/tests/handlers.c b/tests/handlers.c
-index bdb5b0e..5078014 100644
---- a/tests/handlers.c
-+++ b/tests/handlers.c
-@@ -45,6 +45,7 @@
- #  undef NDEBUG /* because test suite relies on assert(...) at the moment */
- #endif
- 
-+#include 
- #include 
- #include 
- #include 
-@@ -407,6 +408,15 @@ long_encoding_handler(void *userData, const XML_Char *encoding,
-   return XML_STATUS_OK;
- }
- 
-+int XMLCALL
-+user_data_checking_unknown_encoding_handler(void *userData,
-+                                            const XML_Char *encoding,
-+                                            XML_Encoding *info) {
-+  const intptr_t number = (intptr_t)userData;
-+  assert_true(number == 0xC0FFEE);
-+  return long_encoding_handler(userData, encoding, info);
-+}
-+
- /* External Entity Handlers */
- 
- int XMLCALL
-diff --git a/tests/handlers.h b/tests/handlers.h
-index 4d6a08d..ac4ca94 100644
---- a/tests/handlers.h
-+++ b/tests/handlers.h
-@@ -159,6 +159,9 @@ extern int XMLCALL long_encoding_handler(void *userData,
-                                          const XML_Char *encoding,
-                                          XML_Encoding *info);
- 
-+extern int XMLCALL user_data_checking_unknown_encoding_handler(
-+    void *userData, const XML_Char *encoding, XML_Encoding *info);
-+
- /* External Entity Handlers */
- 
- typedef struct ExtOption {
--- 
-2.45.4
-
-
-From dea97bc0c894b5494f2e4d46d6076d49dc227e8f Mon Sep 17 00:00:00 2001
-From: Sebastian Pipping 
-Date: Sun, 18 Jan 2026 18:19:25 +0100
-Subject: [PATCH 3/3] Changes: Document CVE-2026-24515
-
--- 
-2.45.4
-
diff --git a/SPECS/expat/CVE-2026-25210.patch b/SPECS/expat/CVE-2026-25210.patch
deleted file mode 100644
index 204ac03cca5..00000000000
--- a/SPECS/expat/CVE-2026-25210.patch
+++ /dev/null
@@ -1,93 +0,0 @@
-From 5ffd029337a8db6b3bef77ecd0a040b3e1e573f2 Mon Sep 17 00:00:00 2001
-From: Matthew Fernandez 
-Date: Thu, 2 Oct 2025 17:15:15 -0700
-Subject: [PATCH 1/3] lib: Make a doubling more readable
-
-Suggested-by: Sebastian Pipping 
----
- lib/xmlparse.c | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/lib/xmlparse.c b/lib/xmlparse.c
-index d804753..a48acd2 100644
---- a/lib/xmlparse.c
-+++ b/lib/xmlparse.c
-@@ -3492,7 +3492,7 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc,
-             tag->name.strLen = convLen;
-             break;
-           }
--          bufSize = (int)(tag->bufEnd - tag->buf) << 1;
-+          bufSize = (int)(tag->bufEnd - tag->buf) * 2;
-           {
-             char *temp = (char *)REALLOC(parser, tag->buf, bufSize);
-             if (temp == NULL)
--- 
-2.45.4
-
-
-From 07d55b4f18ded4740946a9a436e787b3c178176c Mon Sep 17 00:00:00 2001
-From: Matthew Fernandez 
-Date: Thu, 2 Oct 2025 17:15:15 -0700
-Subject: [PATCH 2/3] lib: Realign a size with the `REALLOC` type signature it
- is passed into
-
-Note that this implicitly assumes `tag->bufEnd >= tag->buf`, which should
-already be guaranteed true.
----
- lib/xmlparse.c | 3 +--
- 1 file changed, 1 insertion(+), 2 deletions(-)
-
-diff --git a/lib/xmlparse.c b/lib/xmlparse.c
-index a48acd2..ed505b7 100644
---- a/lib/xmlparse.c
-+++ b/lib/xmlparse.c
-@@ -3481,7 +3481,6 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc,
-         const char *fromPtr = tag->rawName;
-         toPtr = (XML_Char *)tag->buf;
-         for (;;) {
--          int bufSize;
-           int convLen;
-           const enum XML_Convert_Result convert_res
-               = XmlConvert(enc, &fromPtr, rawNameEnd, (ICHAR **)&toPtr,
-@@ -3492,7 +3491,7 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc,
-             tag->name.strLen = convLen;
-             break;
-           }
--          bufSize = (int)(tag->bufEnd - tag->buf) * 2;
-+          const size_t bufSize = (size_t)(tag->bufEnd - tag->buf) * 2;
-           {
-             char *temp = (char *)REALLOC(parser, tag->buf, bufSize);
-             if (temp == NULL)
--- 
-2.45.4
-
-
-From 3776e1554b8b9506387ec8a0591560898fb1ef87 Mon Sep 17 00:00:00 2001
-From: Matthew Fernandez 
-Date: Thu, 2 Oct 2025 17:15:15 -0700
-Subject: [PATCH 3/3] lib: Introduce an integer overflow check for tag buffer
- reallocation
-
-Suggested-by: Sebastian Pipping 
-Signed-off-by: Azure Linux Security Servicing Account 
-Upstream-reference: https://github.com/libexpat/libexpat/pull/1075.patch
----
- lib/xmlparse.c | 2 ++
- 1 file changed, 2 insertions(+)
-
-diff --git a/lib/xmlparse.c b/lib/xmlparse.c
-index ed505b7..0bf913c 100644
---- a/lib/xmlparse.c
-+++ b/lib/xmlparse.c
-@@ -3491,6 +3491,8 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc,
-             tag->name.strLen = convLen;
-             break;
-           }
-+          if (SIZE_MAX / 2 < (size_t)(tag->bufEnd - tag->buf))
-+            return XML_ERROR_NO_MEMORY;
-           const size_t bufSize = (size_t)(tag->bufEnd - tag->buf) * 2;
-           {
-             char *temp = (char *)REALLOC(parser, tag->buf, bufSize);
--- 
-2.45.4
-
diff --git a/SPECS/expat/CVE-2026-32776.patch b/SPECS/expat/CVE-2026-32776.patch
deleted file mode 100644
index e3e370e4d65..00000000000
--- a/SPECS/expat/CVE-2026-32776.patch
+++ /dev/null
@@ -1,88 +0,0 @@
-From bfc23e1f8e08527a455ec4fdd47f410058ff2a7d Mon Sep 17 00:00:00 2001
-From: Francesco Bertolaccini 
-Date: Tue, 3 Mar 2026 16:41:43 +0100
-Subject: [PATCH] Fix NULL function-pointer dereference for empty external
- parameter entities
-
-When an external parameter entity with empty text is referenced inside
-an entity declaration value, the sub-parser created to handle it receives
-0 bytes of input.  Processing enters entityValueInitProcessor which calls
-storeEntityValue() with the parser's encoding; since no bytes were ever
-processed, encoding detection has not yet occurred and the encoding is
-still the initial probing encoding set up by XmlInitEncoding().  That
-encoding only populates scanners[] (for prolog and content), not
-literalScanners[].  XmlEntityValueTok() calls through
-literalScanners[XML_ENTITY_VALUE_LITERAL] which is NULL, causing a
-SEGV.
-
-Skip the tokenization loop entirely when entityTextPtr >= entityTextEnd,
-and initialize the `next` pointer before the early exit so that callers
-(callStoreEntityValue) receive a valid value through nextPtr.
-
-Signed-off-by: Azure Linux Security Servicing Account 
-Upstream-reference: https://github.com/libexpat/libexpat/pull/1158.patch
----
- lib/xmlparse.c      |  9 ++++++++-
- tests/basic_tests.c | 19 +++++++++++++++++++
- 2 files changed, 27 insertions(+), 1 deletion(-)
-
-diff --git a/lib/xmlparse.c b/lib/xmlparse.c
-index 0bf913c..d126f5b 100644
---- a/lib/xmlparse.c
-+++ b/lib/xmlparse.c
-@@ -6765,7 +6765,14 @@ storeEntityValue(XML_Parser parser, const ENCODING *enc,
-       return XML_ERROR_NO_MEMORY;
-   }
- 
--  const char *next;
-+  const char *next = entityTextPtr;
-+
-+  /* Nothing to tokenize. */
-+  if (entityTextPtr >= entityTextEnd) {
-+    result = XML_ERROR_NONE;
-+    goto endEntityValue;
-+  }
-+
-   for (;;) {
-     next
-         = entityTextPtr; /* XmlEntityValueTok doesn't always set the last arg */
-diff --git a/tests/basic_tests.c b/tests/basic_tests.c
-index 2db2a76..cfb09f3 100644
---- a/tests/basic_tests.c
-+++ b/tests/basic_tests.c
-@@ -6123,6 +6123,24 @@ START_TEST(test_varying_buffer_fills) {
- }
- END_TEST
- 
-+START_TEST(test_empty_ext_param_entity_in_value) {
-+  const char *text = "";
-+  ExtOption options[] = {
-+      {XCS("ext.dtd"), ""
-+                       ""},
-+      {XCS("empty"), ""},
-+      {NULL, NULL},
-+  };
-+
-+  XML_SetParamEntityParsing(g_parser, XML_PARAM_ENTITY_PARSING_ALWAYS);
-+  XML_SetExternalEntityRefHandler(g_parser, external_entity_optioner);
-+  XML_SetUserData(g_parser, options);
-+  if (_XML_Parse_SINGLE_BYTES(g_parser, text, (int)strlen(text), XML_TRUE)
-+      == XML_STATUS_ERROR)
-+    xml_failure(g_parser);
-+}
-+END_TEST
-+
- void
- make_basic_test_case(Suite *s) {
-   TCase *tc_basic = tcase_create("basic tests");
-@@ -6368,6 +6386,7 @@ make_basic_test_case(Suite *s) {
-   tcase_add_test(tc_basic, test_empty_element_abort);
-   tcase_add_test__ifdef_xml_dtd(tc_basic,
-                                 test_pool_integrity_with_unfinished_attr);
-+  tcase_add_test__ifdef_xml_dtd(tc_basic, test_empty_ext_param_entity_in_value);
-   tcase_add_test__if_xml_ge(tc_basic, test_entity_ref_no_elements);
-   tcase_add_test__if_xml_ge(tc_basic, test_deep_nested_entity);
-   tcase_add_test__if_xml_ge(tc_basic, test_deep_nested_attribute_entity);
--- 
-2.45.4
-
diff --git a/SPECS/expat/CVE-2026-32777.patch b/SPECS/expat/CVE-2026-32777.patch
deleted file mode 100644
index 437d8cdc101..00000000000
--- a/SPECS/expat/CVE-2026-32777.patch
+++ /dev/null
@@ -1,92 +0,0 @@
-From f86ec6b7d5a7280caeb68d5432fd45ee8f41f274 Mon Sep 17 00:00:00 2001
-From: Sebastian Pipping 
-Date: Sun, 1 Mar 2026 20:16:13 +0100
-Subject: [PATCH 1/2] lib: Reject XML_TOK_INSTANCE_START infinite loop in
- entityValueProcessor
-
-.. that OSS-Fuzz/ClusterFuzz uncovered
-
-Upstream-reference: https://github.com/libexpat/libexpat/pull/1162.patch
----
- lib/xmlparse.c | 11 ++++++++++-
- tests/misc_tests.c | 30 ++++++++++++++++++++++
- 2 file changed, 40 insertions(+), 1 deletion(-)
-
-diff --git a/lib/xmlparse.c b/lib/xmlparse.c
-index d126f5b..88361a7 100644
---- a/lib/xmlparse.c
-+++ b/lib/xmlparse.c
-@@ -5068,7 +5068,7 @@ entityValueInitProcessor(XML_Parser parser, const char *s, const char *end,
-     }
-     /* If we get this token, we have the start of what might be a
-        normal tag, but not a declaration (i.e. it doesn't begin with
--       "";
-+
-+  struct ExtOption options[] = {
-+      {XCS("secondary.txt"),
-+       ""},
-+      {XCS("tertiary.txt"), "
-Date: Sun, 8 Mar 2026 17:28:06 -0700
-Subject: [PATCH 1/2] copy prefix name to pool before lookup
-
-.. so that we cannot end up with a zombie PREFIX in the pool
-that has NULL for a name.
-
-Co-authored-by: Sebastian Pipping 
----
- lib/xmlparse.c | 43 +++++++++++++++++++++++++++++++++++--------
- 1 file changed, 35 insertions(+), 8 deletions(-)
-
-diff --git a/lib/xmlparse.c b/lib/xmlparse.c
-index 88361a7..69027f6 100644
---- a/lib/xmlparse.c
-+++ b/lib/xmlparse.c
-@@ -590,6 +590,8 @@ static XML_Char *poolStoreString(STRING_POOL *pool, const ENCODING *enc,
- static XML_Bool FASTCALL poolGrow(STRING_POOL *pool);
- static const XML_Char *FASTCALL poolCopyString(STRING_POOL *pool,
-                                                const XML_Char *s);
-+static const XML_Char *FASTCALL poolCopyStringNoFinish(STRING_POOL *pool,
-+                                                       const XML_Char *s);
- static const XML_Char *poolCopyStringN(STRING_POOL *pool, const XML_Char *s,
-                                        int n);
- static const XML_Char *FASTCALL poolAppendString(STRING_POOL *pool,
-@@ -7431,16 +7433,24 @@ setContext(XML_Parser parser, const XML_Char *context) {
-       else {
-         if (! poolAppendChar(&parser->m_tempPool, XML_T('\0')))
-           return XML_FALSE;
--        prefix
--            = (PREFIX *)lookup(parser, &dtd->prefixes,
--                               poolStart(&parser->m_tempPool), sizeof(PREFIX));
--        if (! prefix)
-+        const XML_Char *const prefixName = poolCopyStringNoFinish(
-+            &dtd->pool, poolStart(&parser->m_tempPool));
-+        if (! prefixName) {
-           return XML_FALSE;
--        if (prefix->name == poolStart(&parser->m_tempPool)) {
--          prefix->name = poolCopyString(&dtd->pool, prefix->name);
--          if (! prefix->name)
--            return XML_FALSE;
-         }
-+
-+        prefix = (PREFIX *)lookup(parser, &dtd->prefixes, prefixName,
-+                                  sizeof(PREFIX));
-+
-+        const bool prefixNameUsed = prefix && prefix->name == prefixName;
-+        if (prefixNameUsed)
-+          poolFinish(&dtd->pool);
-+        else
-+          poolDiscard(&dtd->pool);
-+
-+        if (! prefix)
-+          return XML_FALSE;
-+
-         poolDiscard(&parser->m_tempPool);
-       }
-       for (context = s + 1; *context != CONTEXT_SEP && *context != XML_T('\0');
-@@ -8029,6 +8039,23 @@ poolCopyString(STRING_POOL *pool, const XML_Char *s) {
-   return s;
- }
- 
-+// A version of `poolCopyString` that does not call `poolFinish`
-+// and reverts any partial advancement upon failure.
-+static const XML_Char *FASTCALL
-+poolCopyStringNoFinish(STRING_POOL *pool, const XML_Char *s) {
-+  const XML_Char *const original = s;
-+  do {
-+    if (! poolAppendChar(pool, *s)) {
-+      // Revert any previously successful advancement
-+      const ptrdiff_t advancedBy = s - original;
-+      if (advancedBy > 0)
-+        pool->ptr -= advancedBy;
-+      return NULL;
-+    }
-+  } while (*s++);
-+  return pool->start;
-+}
-+
- static const XML_Char *
- poolCopyStringN(STRING_POOL *pool, const XML_Char *s, int n) {
-   if (! pool->ptr && ! poolGrow(pool)) {
--- 
-2.45.4
-
-
-From 777bd1a1b2196ac14cbc5efb71a3078583c8dfb2 Mon Sep 17 00:00:00 2001
-From: laserbear <10689391+Laserbear@users.noreply.github.com>
-Date: Sun, 8 Mar 2026 17:28:06 -0700
-Subject: [PATCH 2/2] test that we do not end up with a zombie PREFIX in the
- pool
-
-Signed-off-by: Azure Linux Security Servicing Account 
-Upstream-reference: https://github.com/libexpat/libexpat/pull/1163.patch
----
- tests/nsalloc_tests.c | 27 +++++++++++++++++++++++++++
- 1 file changed, 27 insertions(+)
-
-diff --git a/tests/nsalloc_tests.c b/tests/nsalloc_tests.c
-index a8f5718..d284a58 100644
---- a/tests/nsalloc_tests.c
-+++ b/tests/nsalloc_tests.c
-@@ -1505,6 +1505,32 @@ START_TEST(test_nsalloc_prefixed_element) {
- }
- END_TEST
- 
-+/* Verify that retry after OOM in setContext() does not crash.
-+ */
-+START_TEST(test_nsalloc_setContext_zombie) {
-+  const char *text = "Hello";
-+  unsigned int i;
-+  const unsigned int max_alloc_count = 30;
-+
-+  for (i = 0; i < max_alloc_count; i++) {
-+    g_allocation_count = (int)i;
-+    if (XML_Parse(g_parser, text, (int)strlen(text), XML_TRUE)
-+        != XML_STATUS_ERROR)
-+      break;
-+    /* Retry on the same parser — must not crash */
-+    g_allocation_count = ALLOC_ALWAYS_SUCCEED;
-+    XML_Parse(g_parser, text, (int)strlen(text), XML_TRUE);
-+
-+    nsalloc_teardown();
-+    nsalloc_setup();
-+  }
-+  if (i == 0)
-+    fail("Parsing worked despite failing allocations");
-+  else if (i == max_alloc_count)
-+    fail("Parsing failed even at maximum allocation count");
-+}
-+END_TEST
-+
- void
- make_nsalloc_test_case(Suite *s) {
-   TCase *tc_nsalloc = tcase_create("namespace allocation tests");
-@@ -1539,4 +1565,5 @@ make_nsalloc_test_case(Suite *s) {
-   tcase_add_test__if_xml_ge(tc_nsalloc, test_nsalloc_long_default_in_ext);
-   tcase_add_test(tc_nsalloc, test_nsalloc_long_systemid_in_ext);
-   tcase_add_test(tc_nsalloc, test_nsalloc_prefixed_element);
-+  tcase_add_test(tc_nsalloc, test_nsalloc_setContext_zombie);
- }
--- 
-2.45.4
-
diff --git a/SPECS/expat/Stop-updating-event-pointer-on-exit-for-reentry.patch b/SPECS/expat/Stop-updating-event-pointer-on-exit-for-reentry.patch
deleted file mode 100644
index 6df6758fbcc..00000000000
--- a/SPECS/expat/Stop-updating-event-pointer-on-exit-for-reentry.patch
+++ /dev/null
@@ -1,256 +0,0 @@
-From 89a9c6807c982b4fa8aa806dd72771d6642dd8a1 Mon Sep 17 00:00:00 2001
-From: Berkay Eren Ürün m_parsingStatus.parsing) {
-     case XML_SUSPENDED:
-+      *eventPP = next;
-       *nextPtr = next;
-       return XML_ERROR_NONE;
-     case XML_FINISHED:
-+      *eventPP = next;
-       return XML_ERROR_ABORTED;
-     case XML_PARSING:
-       if (parser->m_reenter) {
-@@ -3768,6 +3769,7 @@ doContent(XML_Parser parser, int startTagLevel, const ENCODING *enc,
-       }
-       /* Fall through */
-     default:;
-+      *eventPP = s = next;
-     }
-   }
-   /* not reached */
-@@ -4684,12 +4686,13 @@ doCdataSection(XML_Parser parser, const ENCODING *enc, const char **startPtr,
-       /* LCOV_EXCL_STOP */
-     }
- 
--    *eventPP = s = next;
-     switch (parser->m_parsingStatus.parsing) {
-     case XML_SUSPENDED:
-+      *eventPP = next;
-       *nextPtr = next;
-       return XML_ERROR_NONE;
-     case XML_FINISHED:
-+      *eventPP = next;
-       return XML_ERROR_ABORTED;
-     case XML_PARSING:
-       if (parser->m_reenter) {
-@@ -4697,6 +4700,7 @@ doCdataSection(XML_Parser parser, const ENCODING *enc, const char **startPtr,
-       }
-       /* Fall through */
-     default:;
-+      *eventPP = s = next;
-     }
-   }
-   /* not reached */
-@@ -6307,12 +6311,13 @@ epilogProcessor(XML_Parser parser, const char *s, const char *end,
-     default:
-       return XML_ERROR_JUNK_AFTER_DOC_ELEMENT;
-     }
--    parser->m_eventPtr = s = next;
-     switch (parser->m_parsingStatus.parsing) {
-     case XML_SUSPENDED:
-+      parser->m_eventPtr = next;
-       *nextPtr = next;
-       return XML_ERROR_NONE;
-     case XML_FINISHED:
-+      parser->m_eventPtr = next;
-       return XML_ERROR_ABORTED;
-     case XML_PARSING:
-       if (parser->m_reenter) {
-@@ -6320,6 +6325,7 @@ epilogProcessor(XML_Parser parser, const char *s, const char *end,
-       }
-     /* Fall through */
-     default:;
-+      parser->m_eventPtr = s = next;
-     }
-   }
- }
-diff --git a/tests/common.c b/tests/common.c
-index 3aea8d7..b267dbb 100644
---- a/tests/common.c
-+++ b/tests/common.c
-@@ -42,6 +42,8 @@
- */
- 
- #include 
-+#include 
-+#include  // for SIZE_MAX
- #include 
- #include 
- 
-@@ -294,3 +296,26 @@ duff_reallocator(void *ptr, size_t size) {
-     g_reallocation_count--;
-   return realloc(ptr, size);
- }
-+
-+// Portable remake of strndup(3) for C99; does not care about space efficiency
-+char *
-+portable_strndup(const char *s, size_t n) {
-+  if ((s == NULL) || (n == SIZE_MAX)) {
-+    errno = EINVAL;
-+    return NULL;
-+  }
-+
-+  char *const buffer = (char *)malloc(n + 1);
-+  if (buffer == NULL) {
-+    errno = ENOMEM;
-+    return NULL;
-+  }
-+
-+  errno = 0;
-+
-+  memcpy(buffer, s, n);
-+
-+  buffer[n] = '\0';
-+
-+  return buffer;
-+}
-diff --git a/tests/common.h b/tests/common.h
-index bc4c7da..8871130 100644
---- a/tests/common.h
-+++ b/tests/common.h
-@@ -146,6 +146,8 @@ extern void *duff_allocator(size_t size);
- 
- extern void *duff_reallocator(void *ptr, size_t size);
- 
-+extern char *portable_strndup(const char *s, size_t n);
-+
- #endif /* XML_COMMON_H */
- 
- #ifdef __cplusplus
-diff --git a/tests/misc_tests.c b/tests/misc_tests.c
-index f9a78f6..2b9f793 100644
---- a/tests/misc_tests.c
-+++ b/tests/misc_tests.c
-@@ -561,6 +561,66 @@ START_TEST(test_renter_loop_finite_content) {
- }
- END_TEST
- 
-+// Inspired by function XML_OriginalString of Perl's XML::Parser
-+static char *
-+dup_original_string(XML_Parser parser) {
-+  const int byte_count = XML_GetCurrentByteCount(parser);
-+
-+  assert_true(byte_count >= 0);
-+
-+  int offset = -1;
-+  int size = -1;
-+
-+  const char *const context = XML_GetInputContext(parser, &offset, &size);
-+
-+#if XML_CONTEXT_BYTES > 0
-+  assert_true(context != NULL);
-+  assert_true(offset >= 0);
-+  assert_true(size >= 0);
-+  return portable_strndup(context + offset, byte_count);
-+#else
-+  assert_true(context == NULL);
-+  return NULL;
-+#endif
-+}
-+
-+static void
-+on_characters_issue_980(void *userData, const XML_Char *s, int len) {
-+  (void)s;
-+  (void)len;
-+  XML_Parser parser = (XML_Parser)userData;
-+
-+  char *const original_string = dup_original_string(parser);
-+
-+#if XML_CONTEXT_BYTES > 0
-+  assert_true(original_string != NULL);
-+  assert_true(strcmp(original_string, "&draft.day;") == 0);
-+  free(original_string);
-+#else
-+  assert_true(original_string == NULL);
-+#endif
-+}
-+
-+START_TEST(test_misc_expected_event_ptr_issue_980) {
-+  // NOTE: This is a tiny subset of sample "REC-xml-19980210.xml"
-+  //       from Perl's XML::Parser
-+  const char *const doc = "\n"
-+                          "]>\n"
-+                          "&draft.day;\n";
-+
-+  XML_Parser parser = XML_ParserCreate(NULL);
-+  XML_SetUserData(parser, parser);
-+  XML_SetCharacterDataHandler(parser, on_characters_issue_980);
-+
-+  assert_true(_XML_Parse_SINGLE_BYTES(parser, doc, (int)strlen(doc),
-+                                      /*isFinal=*/XML_TRUE)
-+              == XML_STATUS_OK);
-+
-+  XML_ParserFree(parser);
-+}
-+END_TEST
-+
- void
- make_miscellaneous_test_case(Suite *s) {
-   TCase *tc_misc = tcase_create("miscellaneous tests");
-@@ -588,4 +648,5 @@ make_miscellaneous_test_case(Suite *s) {
-   tcase_add_test(tc_misc, test_misc_resumeparser_not_crashing);
-   tcase_add_test(tc_misc, test_misc_stopparser_rejects_unstarted_parser);
-   tcase_add_test__if_xml_ge(tc_misc, test_renter_loop_finite_content);
-+  tcase_add_test(tc_misc, test_misc_expected_event_ptr_issue_980);
- }
--- 
-2.45.4
-
diff --git a/SPECS/expat/expat.signatures.json b/SPECS/expat/expat.signatures.json
index faaee12cd64..1d591f132d4 100644
--- a/SPECS/expat/expat.signatures.json
+++ b/SPECS/expat/expat.signatures.json
@@ -1,5 +1,5 @@
 {
  "Signatures": {
-  "expat-2.6.4.tar.bz2": "8dc480b796163d4436e6f1352e71800a774f73dbae213f1860b60607d2a83ada"
+  "expat-2.8.1.tar.bz2": "f5833dd2e1cd7739ec9182804a1a29c4f0cc7c2f26b633d3a2188b7766a88ecb"
  }
 }
\ No newline at end of file
diff --git a/SPECS/expat/expat.spec b/SPECS/expat/expat.spec
index 59f199cb0e4..bc89f710efe 100644
--- a/SPECS/expat/expat.spec
+++ b/SPECS/expat/expat.spec
@@ -1,22 +1,14 @@
 %define         underscore_version %(echo %{version} | cut -d. -f1-3 --output-delimiter="_")
 Summary:        An XML parser library
 Name:           expat
-Version:        2.6.4
-Release:        6%{?dist}
+Version:        2.8.1
+Release:        1%{?dist}
 License:        MIT
 Vendor:         Microsoft Corporation
 Distribution:   Azure Linux
 Group:          System Environment/GeneralLibraries
 URL:            https://libexpat.github.io/
 Source0:        https://github.com/libexpat/libexpat/releases/download/R_%{underscore_version}/%{name}-%{version}.tar.bz2
-Patch0:         CVE-2024-8176.patch
-Patch1:         CVE-2025-59375.patch
-Patch2:         CVE-2026-24515.patch
-Patch3:         CVE-2026-25210.patch
-Patch4:         Stop-updating-event-pointer-on-exit-for-reentry.patch
-Patch5:         CVE-2026-32776.patch
-Patch6:         CVE-2026-32777.patch
-Patch7:         CVE-2026-32778.patch
 Requires:       %{name}-libs = %{version}-%{release}
 
 %description
@@ -74,6 +66,9 @@ rm -rf %{buildroot}/%{_docdir}/%{name}
 %{_libdir}/libexpat.so.1*
 
 %changelog
+* Mon Jun 22 2026 BinduSri Adabala  - 2.8.1-1
+- Upgrade to 2.8.1 to fix CVE-2026-7210
+
 * Wed Apr 15 2026 Azure Linux Security Servicing Account  - 2.6.4-6
 - Patch for CVE-2026-32778, CVE-2026-32777, CVE-2026-32776
 
diff --git a/cgmanifest.json b/cgmanifest.json
index 4612bf55249..88d58fcb8f1 100644
--- a/cgmanifest.json
+++ b/cgmanifest.json
@@ -3518,8 +3518,8 @@
         "type": "other",
         "other": {
           "name": "expat",
-          "version": "2.6.4",
-          "downloadUrl": "https://github.com/libexpat/libexpat/releases/download/R_2_6_4/expat-2.6.4.tar.bz2"
+          "version": "2.8.1",
+          "downloadUrl": "https://github.com/libexpat/libexpat/releases/download/R_2_8_1/expat-2.8.1.tar.bz2"
         }
       }
     },
diff --git a/toolkit/resources/manifests/package/pkggen_core_aarch64.txt b/toolkit/resources/manifests/package/pkggen_core_aarch64.txt
index 460e60c208f..0ee77d1bc0f 100644
--- a/toolkit/resources/manifests/package/pkggen_core_aarch64.txt
+++ b/toolkit/resources/manifests/package/pkggen_core_aarch64.txt
@@ -99,9 +99,9 @@ elfutils-libelf-0.189-6.azl3.aarch64.rpm
 elfutils-libelf-devel-0.189-6.azl3.aarch64.rpm
 elfutils-libelf-devel-static-0.189-6.azl3.aarch64.rpm
 elfutils-libelf-lang-0.189-6.azl3.aarch64.rpm
-expat-2.6.4-6.azl3.aarch64.rpm
-expat-devel-2.6.4-6.azl3.aarch64.rpm
-expat-libs-2.6.4-6.azl3.aarch64.rpm
+expat-2.8.1-1.azl3.aarch64.rpm
+expat-devel-2.8.1-1.azl3.aarch64.rpm
+expat-libs-2.8.1-1.azl3.aarch64.rpm
 libpipeline-1.5.7-1.azl3.aarch64.rpm
 libpipeline-devel-1.5.7-1.azl3.aarch64.rpm
 gdbm-1.23-1.azl3.aarch64.rpm
diff --git a/toolkit/resources/manifests/package/pkggen_core_x86_64.txt b/toolkit/resources/manifests/package/pkggen_core_x86_64.txt
index 0d7b3bdb00d..092ec66b739 100644
--- a/toolkit/resources/manifests/package/pkggen_core_x86_64.txt
+++ b/toolkit/resources/manifests/package/pkggen_core_x86_64.txt
@@ -99,9 +99,9 @@ elfutils-libelf-0.189-6.azl3.x86_64.rpm
 elfutils-libelf-devel-0.189-6.azl3.x86_64.rpm
 elfutils-libelf-devel-static-0.189-6.azl3.x86_64.rpm
 elfutils-libelf-lang-0.189-6.azl3.x86_64.rpm
-expat-2.6.4-6.azl3.x86_64.rpm
-expat-devel-2.6.4-6.azl3.x86_64.rpm
-expat-libs-2.6.4-6.azl3.x86_64.rpm
+expat-2.8.1-1.azl3.x86_64.rpm
+expat-devel-2.8.1-1.azl3.x86_64.rpm
+expat-libs-2.8.1-1.azl3.x86_64.rpm
 libpipeline-1.5.7-1.azl3.x86_64.rpm
 libpipeline-devel-1.5.7-1.azl3.x86_64.rpm
 gdbm-1.23-1.azl3.x86_64.rpm
diff --git a/toolkit/resources/manifests/package/toolchain_aarch64.txt b/toolkit/resources/manifests/package/toolchain_aarch64.txt
index 7ab6383f9fc..c98f2ab337a 100644
--- a/toolkit/resources/manifests/package/toolchain_aarch64.txt
+++ b/toolkit/resources/manifests/package/toolchain_aarch64.txt
@@ -94,10 +94,10 @@ elfutils-libelf-0.189-6.azl3.aarch64.rpm
 elfutils-libelf-devel-0.189-6.azl3.aarch64.rpm
 elfutils-libelf-devel-static-0.189-6.azl3.aarch64.rpm
 elfutils-libelf-lang-0.189-6.azl3.aarch64.rpm
-expat-2.6.4-6.azl3.aarch64.rpm
-expat-debuginfo-2.6.4-6.azl3.aarch64.rpm
-expat-devel-2.6.4-6.azl3.aarch64.rpm
-expat-libs-2.6.4-6.azl3.aarch64.rpm
+expat-2.8.1-1.azl3.aarch64.rpm
+expat-debuginfo-2.8.1-1.azl3.aarch64.rpm
+expat-devel-2.8.1-1.azl3.aarch64.rpm
+expat-libs-2.8.1-1.azl3.aarch64.rpm
 file-5.45-1.azl3.aarch64.rpm
 file-debuginfo-5.45-1.azl3.aarch64.rpm
 file-devel-5.45-1.azl3.aarch64.rpm
diff --git a/toolkit/resources/manifests/package/toolchain_x86_64.txt b/toolkit/resources/manifests/package/toolchain_x86_64.txt
index e0886b97054..52d6321c700 100644
--- a/toolkit/resources/manifests/package/toolchain_x86_64.txt
+++ b/toolkit/resources/manifests/package/toolchain_x86_64.txt
@@ -99,10 +99,10 @@ elfutils-libelf-0.189-6.azl3.x86_64.rpm
 elfutils-libelf-devel-0.189-6.azl3.x86_64.rpm
 elfutils-libelf-devel-static-0.189-6.azl3.x86_64.rpm
 elfutils-libelf-lang-0.189-6.azl3.x86_64.rpm
-expat-2.6.4-6.azl3.x86_64.rpm
-expat-debuginfo-2.6.4-6.azl3.x86_64.rpm
-expat-devel-2.6.4-6.azl3.x86_64.rpm
-expat-libs-2.6.4-6.azl3.x86_64.rpm
+expat-2.8.1-1.azl3.x86_64.rpm
+expat-debuginfo-2.8.1-1.azl3.x86_64.rpm
+expat-devel-2.8.1-1.azl3.x86_64.rpm
+expat-libs-2.8.1-1.azl3.x86_64.rpm
 file-5.45-1.azl3.x86_64.rpm
 file-debuginfo-5.45-1.azl3.x86_64.rpm
 file-devel-5.45-1.azl3.x86_64.rpm