From 2fc4b29f0dfdcd81564d2d94bb66f2b512400614 Mon Sep 17 00:00:00 2001 From: zhuofeng Date: Sat, 29 Mar 2025 17:01:17 +0800 Subject: [PATCH] fix CVE-2024-8176 --- backport-001-CVE-2024-8176.patch | 202 +++++++++++++++++++++++++ backport-002-CVE-2024-8176.patch | 31 ++++ backport-003-CVE-2024-8176.patch | 163 ++++++++++++++++++++ backport-004-CVE-2024-8176.patch | 89 +++++++++++ backport-005-CVE-2024-8176.patch | 247 ++++++++++++++++++++++++++++++ backport-006-CVE-2024-8176.patch | 105 +++++++++++++ backport-007-CVE-2024-8176.patch | 249 +++++++++++++++++++++++++++++++ backport-008-CVE-2024-8176.patch | 229 ++++++++++++++++++++++++++++ backport-009-CVE-2024-8176.patch | 199 ++++++++++++++++++++++++ backport-010-CVE-2024-8176.patch | 133 +++++++++++++++++ backport-011-CVE-2024-8176.patch | 53 +++++++ expat.spec | 16 +- 12 files changed, 1715 insertions(+), 1 deletion(-) create mode 100644 backport-001-CVE-2024-8176.patch create mode 100644 backport-002-CVE-2024-8176.patch create mode 100644 backport-003-CVE-2024-8176.patch create mode 100644 backport-004-CVE-2024-8176.patch create mode 100644 backport-005-CVE-2024-8176.patch create mode 100644 backport-006-CVE-2024-8176.patch create mode 100644 backport-007-CVE-2024-8176.patch create mode 100644 backport-008-CVE-2024-8176.patch create mode 100644 backport-009-CVE-2024-8176.patch create mode 100644 backport-010-CVE-2024-8176.patch create mode 100644 backport-011-CVE-2024-8176.patch diff --git a/backport-001-CVE-2024-8176.patch b/backport-001-CVE-2024-8176.patch new file mode 100644 index 0000000..e7f0931 --- /dev/null +++ b/backport-001-CVE-2024-8176.patch @@ -0,0 +1,202 @@ +From cf6dc8e885feef5d5ce2225bd1d2c535162b7e39 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Berkay=20Eren=20=C3=9Cr=C3=BCn?= +Date: Sat, 10 Aug 2024 21:00:00 +0200 +Subject: [PATCH] Introduce reenter flag + +Co-authored-by: Jann Horn + +Add a new reenter flag. This flag acts like XML_SUSPENDED, +except that instead of returning out of the library, we +only return back to the main parse function, then re-enter +the processor function. +--- + lib/xmlparse.c | 88 ++++++++++++++++++++++++++++++++++++++++++++------ + 1 file changed, 79 insertions(+), 9 deletions(-) + +diff --git a/lib/xmlparse.c b/lib/xmlparse.c +index e13b2bf..a94aa38 100644 +--- a/lib/xmlparse.c ++++ b/lib/xmlparse.c +@@ -716,6 +716,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))) +@@ -974,7 +975,28 @@ callProcessor(XML_Parser parser, const char *start, const char *end, + return XML_ERROR_NONE; + } + } +- 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) { +@@ -1196,6 +1218,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; +@@ -2203,6 +2227,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) +@@ -2764,6 +2793,12 @@ 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) { ++ *endPtr = next; ++ return XML_ERROR_NONE; ++ } ++ /* Fall through */ + default: + start = next; + } +@@ -3063,7 +3098,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); +@@ -3124,7 +3161,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); +@@ -3264,6 +3303,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:; + } + } +@@ -4188,6 +4233,12 @@ 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) { ++ *nextPtr = next; ++ return XML_ERROR_NONE; ++ } ++ /* Fall through */ + default:; + } + } +@@ -5722,6 +5773,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); +@@ -5796,6 +5853,12 @@ 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) { ++ *nextPtr = next; ++ return XML_ERROR_NONE; ++ } ++ /* Fall through */ + default:; + } + } +@@ -5897,8 +5960,10 @@ internalEntityProcessor(XML_Parser parser, const char *s, const char *end, + + if (result != XML_ERROR_NONE) + return result; +- +- if (textEnd != next && parser->m_parsingStatus.parsing == XML_SUSPENDED) { ++ 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; + } +@@ -5912,11 +5977,16 @@ internalEntityProcessor(XML_Parser parser, const char *s, const char *end, + 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 the state is XML_SUSPENDED and 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 the state is XML_PARSING, m_reenter is set, and there are more open ++ // entities, we want to return and reenter to internalEntityProcessor to ++ // process the next entity in the list + if (parser->m_openInternalEntities != NULL +- && parser->m_parsingStatus.parsing == XML_SUSPENDED) { ++ && (parser->m_parsingStatus.parsing == XML_SUSPENDED ++ || (parser->m_parsingStatus.parsing == XML_PARSING ++ && parser->m_reenter))) { + return XML_ERROR_NONE; + } + +-- +2.33.0 + diff --git a/backport-002-CVE-2024-8176.patch b/backport-002-CVE-2024-8176.patch new file mode 100644 index 0000000..b51a90b --- /dev/null +++ b/backport-002-CVE-2024-8176.patch @@ -0,0 +1,31 @@ +From 6edca2c37e280b3ce15e776ffabf70f11abf2966 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Berkay=20Eren=20=C3=9Cr=C3=BCn?= +Date: Thu, 12 Dec 2024 14:06:44 +0100 +Subject: [PATCH 2/7] Switch allowClosingDoctype + +This change of allowClosingDoctype has no effect and only serves as a +preparation for the upcoming changes. + +Reference: https://github.com/libexpat/libexpat/pull/973/commits/6edca2c37e280b3ce15e776ffabf70f11abf2966 +Conflict: adapt internalEntityProcessor + +--- + lib/xmlparse.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/xmlparse.c b/lib/xmlparse.c +index 29843b0..10225e3 100644 +--- a/lib/xmlparse.c ++++ b/lib/xmlparse.c +@@ -5944,7 +5944,7 @@ internalEntityProcessor(XML_Parser parser, const char *s, const char *end, + int tok + = XmlPrologTok(parser->m_internalEncoding, textStart, textEnd, &next); + result = doProlog(parser, parser->m_internalEncoding, textStart, textEnd, +- tok, next, &next, XML_FALSE, XML_TRUE, ++ tok, next, &next, XML_FALSE, XML_FALSE, + XML_ACCOUNT_ENTITY_EXPANSION); + } else + #endif /* XML_DTD */ +-- +2.33.0 + diff --git a/backport-003-CVE-2024-8176.patch b/backport-003-CVE-2024-8176.patch new file mode 100644 index 0000000..c56a7bc --- /dev/null +++ b/backport-003-CVE-2024-8176.patch @@ -0,0 +1,163 @@ +From 95aa319b65b97d8a0abb5a2eda6295c3047f2bf9 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Berkay=20Eren=20=C3=9Cr=C3=BCn?= +Date: Sun, 11 Aug 2024 21:00:00 +0200 +Subject: [PATCH] Fix internal entity processing + +Co-authored-by: Jann Horn + +This avoids unbounded recursion in internal entity processing + +Reference: https://github.com/libexpat/libexpat/pull/973/commits/a910fbc0e1c3828702c75001647ea98b06f8acd9 +Conflict: adapt processInternalEntity, internalEntityProcessor +--- + lib/xmlparse.c | 91 +++++++++++++++++--------------------------------- + 1 file changed, 31 insertions(+), 60 deletions(-) + +diff --git a/lib/xmlparse.c b/lib/xmlparse.c +index 9de7676..a6f1ce2 100644 +--- a/lib/xmlparse.c ++++ b/lib/xmlparse.c +@@ -2704,8 +2704,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; +@@ -5866,9 +5867,6 @@ epilogProcessor(XML_Parser parser, const char *s, const char *end, + + 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; + + if (parser->m_freeInternalEntities) { +@@ -5885,6 +5883,7 @@ processInternalEntity(XML_Parser parser, ENTITY *entity, XML_Bool betweenDecl) { + entityTrackingOnOpen(parser, entity, __LINE__); + #endif + entity->processed = 0; ++ parser->m_processor = internalEntityProcessor; + openEntity->next = parser->m_openInternalEntities; + parser->m_openInternalEntities = openEntity; + openEntity->entity = entity; +@@ -5892,45 +5891,15 @@ processInternalEntity(XML_Parser parser, ENTITY *entity, XML_Bool betweenDecl) { + 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; +- +-#ifdef XML_DTD +- 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 +-#endif /* XML_DTD */ +- 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 defined(XML_DTD) || XML_GE == 1 +- entityTrackingOnClose(parser, entity, __LINE__); +-#endif /* defined(XML_DTD) || 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; +- } +- } +- return result; ++ triggerReenter(parser); ++ 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); + ENTITY *entity; + const char *textStart, *textEnd; + const char *next; +@@ -5972,7 +5941,19 @@ internalEntityProcessor(XML_Parser parser, const char *s, const char *end, + entityTrackingOnClose(parser, entity, __LINE__); + #endif + entity->open = XML_FALSE; +- parser->m_openInternalEntities = openEntity->next; ++ // Remove fully processed openEntity from open entity list. doContent call can ++ // add maximum one new entity to m_openInternalEntities since when a new ++ // entity is detected, parser will go into REENTER state and return. Therefore ++ // openEntity is either the head or next to the new head. ++ if (parser->m_openInternalEntities == openEntity) { ++ // No new entity detected during entity processing, ++ // openEntity is still the head. ++ parser->m_openInternalEntities = parser->m_openInternalEntities->next; ++ } else { ++ // New entity detected since list has a new head. openEntity is the second ++ // element. ++ parser->m_openInternalEntities->next = openEntity->next; ++ } + /* put openEntity back in list of free instances */ + openEntity->next = parser->m_freeInternalEntities; + parser->m_freeInternalEntities = openEntity; +@@ -5990,29 +5971,19 @@ internalEntityProcessor(XML_Parser parser, const char *s, const char *end, + return XML_ERROR_NONE; + } + +-#ifdef XML_DTD +- 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 +-#endif /* XML_DTD */ +- { +- 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; ++ if (parser->m_openInternalEntities == NULL) { ++ parser->m_processor = entity->is_param ? prologProcessor : contentProcessor; ++ // internalEntityProcessor is called from callProcessor's while(1) loop, ++ // therefore "end" denotes callProcessor's "end", which denotes the end ++ // of the current buffer being parsed. Consequently, if we do not have ++ // any open entities left and have reached to the end, we must not ++ // trigger a reentry. ++ if (end == *nextPtr) { ++ return XML_ERROR_NONE; + } +- return result; + } ++ triggerReenter(parser); ++ return XML_ERROR_NONE; + } + + static enum XML_Error PTRCALL +-- +2.33.0 + diff --git a/backport-004-CVE-2024-8176.patch b/backport-004-CVE-2024-8176.patch new file mode 100644 index 0000000..3a8a469 --- /dev/null +++ b/backport-004-CVE-2024-8176.patch @@ -0,0 +1,89 @@ +From 74308916d90218707bd7e8f61fbf52032e0e633d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Berkay=20Eren=20=C3=9Cr=C3=BCn?= +Date: Wed, 25 Sep 2024 09:34:48 +0200 +Subject: [PATCH 4/7] Add next pointer to appendAttributeValue + +This commits extends appendAttributeValue by introducing a new parameter +that will be set to the next token to process. + +Having such a parameter allows us to reenter the function after an exit +and continue from the last token pointed by the pointer. + +Reference: https://github.com/libexpat/libexpat/pull/973/commits/74308916d90218707bd7e8f61fbf52032e0e633d +Conflict: adapt appendAttributeValue + +--- + lib/xmlparse.c | 22 ++++++++++++++-------- + 1 file changed, 14 insertions(+), 8 deletions(-) + +diff --git a/lib/xmlparse.c b/lib/xmlparse.c +index cc5a609..781fc71 100644 +--- a/lib/xmlparse.c ++++ b/lib/xmlparse.c +@@ -486,10 +486,10 @@ static enum XML_Error storeAttributeValue(XML_Parser parser, const ENCODING *, + XML_Bool isCdata, const char *, + const char *, STRING_POOL *, + enum XML_Account account); +-static enum XML_Error appendAttributeValue(XML_Parser parser, const ENCODING *, +- XML_Bool isCdata, const char *, +- const char *, STRING_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 *); +@@ -5994,8 +5994,8 @@ 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); ++ enum XML_Error result = appendAttributeValue(parser, enc, isCdata, ptr, end, ++ pool, account, NULL); + if (result) + return result; + if (! isCdata && poolLength(pool) && poolLastChar(pool) == 0x20) +@@ -6008,7 +6008,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); +@@ -6026,6 +6026,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) +@@ -6174,7 +6177,7 @@ appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata, + result = appendAttributeValue(parser, parser->m_internalEncoding, + isCdata, (const char *)entity->textPtr, + (const char *)textEnd, pool, +- XML_ACCOUNT_ENTITY_EXPANSION); ++ XML_ACCOUNT_ENTITY_EXPANSION, NULL); + #if defined(XML_DTD) || XML_GE == 1 + entityTrackingOnClose(parser, entity, __LINE__); + #endif +@@ -6201,6 +6204,9 @@ appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata, + /* LCOV_EXCL_STOP */ + } + ptr = next; ++ if (nextPtr) { ++ *nextPtr = next; ++ } + } + /* not reached */ + } +-- +2.33.0 + diff --git a/backport-005-CVE-2024-8176.patch b/backport-005-CVE-2024-8176.patch new file mode 100644 index 0000000..08416fe --- /dev/null +++ b/backport-005-CVE-2024-8176.patch @@ -0,0 +1,247 @@ +From da10ca23282cf336553307dcc47dafb5b7378ad0 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Berkay=20Eren=20=C3=9Cr=C3=BCn?= +Date: Tue, 27 Aug 2024 17:14:50 +0200 +Subject: [PATCH 5/7] Break cyclic appendAttributeValue recursion + +During processing attributes with entity references, +appendAttributeValue can reach high recursion depths that can lead to +a crash. + +This commit switches the processing to an iterative approach similar to +the fix for internal entity processing. A new m_openAttributeEntities +list is introduced to keep track of entity references that need +processing. When a new entity reference is detected, instead of calling +appendAttributeValue recursively, the entity will be added to open +entities list and the execution will return to storeAttributeValue, +where newly added entity will be handled. After the entity processing +is done, appendAttributeValue will be called by using the next token. + +Reference: https://github.com/libexpat/libexpat/pull/973/commits/da10ca23282cf336553307dcc47dafb5b7378ad0 +Conflict: adapt appendAttributeValue + +--- + lib/xmlparse.c | 145 +++++++++++++++++++++++++++++++++++++++++++------ + 1 file changed, 127 insertions(+), 18 deletions(-) + +diff --git a/lib/xmlparse.c b/lib/xmlparse.c +index 781fc71..c86da91 100644 +--- a/lib/xmlparse.c ++++ b/lib/xmlparse.c +@@ -457,6 +457,8 @@ static enum XML_Error doProlog(XML_Parser parser, const ENCODING *enc, + enum XML_Account account); + static enum XML_Error processInternalEntity(XML_Parser parser, ENTITY *entity, + XML_Bool betweenDecl); ++static enum XML_Error processAttributeEntity(XML_Parser parser, ENTITY *entity, ++ XML_Bool betweenDecl); + static enum XML_Error doContent(XML_Parser parser, int startTagLevel, + const ENCODING *enc, const char *start, + const char *end, const char **endPtr, +@@ -665,6 +667,8 @@ 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; + XML_Bool m_defaultExpandInternalEntities; + int m_tagLevel; + ENTITY *m_declEntity; +@@ -1103,6 +1107,7 @@ parserCreate(const XML_Char *encodingName, + parser->m_freeBindingList = NULL; + parser->m_freeTagList = NULL; + parser->m_freeInternalEntities = NULL; ++ parser->m_freeAttributeEntities = NULL; + + parser->m_groupSize = 0; + parser->m_groupConnector = NULL; +@@ -1204,6 +1209,7 @@ 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_defaultExpandInternalEntities = XML_TRUE; + parser->m_tagLevel = 0; + parser->m_tagStack = NULL; +@@ -1275,6 +1281,15 @@ 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; ++ } + moveToFreeBindingList(parser, parser->m_inheritedBindings); + FREE(parser, parser->m_unknownEncodingMem); + if (parser->m_unknownEncodingRelease) +@@ -1531,7 +1546,20 @@ 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); ++ } + destroyBindings(parser->m_freeBindingList, parser); + destroyBindings(parser->m_inheritedBindings, parser); + poolDestroy(&parser->m_tempPool); +@@ -5859,6 +5887,7 @@ epilogProcessor(XML_Parser parser, const char *s, const char *end, + } + } + ++/* KEEP IN SYNC with processAttributeEntity */ + static enum XML_Error + processInternalEntity(XML_Parser parser, ENTITY *entity, XML_Bool betweenDecl) { + OPEN_INTERNAL_ENTITY *openEntity; +@@ -5890,6 +5919,37 @@ processInternalEntity(XML_Parser parser, ENTITY *entity, XML_Bool betweenDecl) { + return XML_ERROR_NONE; + } + ++/* KEEP IN SYNC with processInternalEntity */ ++static enum XML_Error ++processAttributeEntity(XML_Parser parser, ENTITY *entity, ++ XML_Bool betweenDecl) { ++ OPEN_INTERNAL_ENTITY *openEntity; ++ ++ if (parser->m_freeAttributeEntities) { ++ openEntity = parser->m_freeAttributeEntities; ++ parser->m_freeAttributeEntities = openEntity->next; ++ } else { ++ openEntity ++ = (OPEN_INTERNAL_ENTITY *)MALLOC(parser, sizeof(OPEN_INTERNAL_ENTITY)); ++ if (! openEntity) ++ return XML_ERROR_NO_MEMORY; ++ } ++ entity->open = XML_TRUE; ++#if XML_GE == 1 ++ entityTrackingOnOpen(parser, entity, __LINE__); ++#endif ++ entity->processed = 0; ++ openEntity->next = parser->m_openAttributeEntities; ++ parser->m_openAttributeEntities = openEntity; ++ openEntity->entity = entity; ++ openEntity->startTagLevel = parser->m_tagLevel; ++ openEntity->betweenDecl = betweenDecl; ++ openEntity->internalEventPtr = NULL; ++ openEntity->internalEventEndPtr = NULL; ++ ++ return XML_ERROR_NONE; ++} ++ + static enum XML_Error PTRCALL + internalEntityProcessor(XML_Parser parser, const char *s, const char *end, + const char **nextPtr) { +@@ -5994,8 +6054,67 @@ 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, NULL); ++ 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; ++ 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; ++ } ++ ++#if XML_GE == 1 ++ entityTrackingOnClose(parser, entity, __LINE__); ++#endif ++ entity->open = XML_FALSE; ++ // Remove fully processed openEntity from open entity list. ++ // appendAttributeValue call can add maximum one new entity to ++ // m_openAttributeEntities since when a new entity is detected, parser ++ // will set the 'reenter' flag and return. Therefore openEntity is either ++ // the head or next to the new head. ++ if (parser->m_openAttributeEntities == openEntity) { ++ // No new entity detected during entity processing, ++ // openEntity is still the head. ++ parser->m_openAttributeEntities = parser->m_openAttributeEntities->next; ++ } else { ++ // New entity detected since list has a new head. openEntity is the ++ // second element. ++ parser->m_openAttributeEntities->next = openEntity->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) +@@ -6169,21 +6288,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 defined(XML_DTD) || 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, NULL); +-#if defined(XML_DTD) || XML_GE == 1 +- entityTrackingOnClose(parser, entity, __LINE__); +-#endif +- entity->open = XML_FALSE; +- if (result) +- return result; ++ result = processAttributeEntity(parser, entity, XML_FALSE); ++ if (nextPtr) { ++ *nextPtr = next; ++ } ++ return result; + } + } break; + default: +-- +2.33.0 + diff --git a/backport-006-CVE-2024-8176.patch b/backport-006-CVE-2024-8176.patch new file mode 100644 index 0000000..381caa7 --- /dev/null +++ b/backport-006-CVE-2024-8176.patch @@ -0,0 +1,105 @@ +From bf97ac508110dc390bd5471ed4904d5a4044332b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Berkay=20Eren=20=C3=9Cr=C3=BCn?= +Date: Mon, 7 Oct 2024 15:38:53 +0200 +Subject: [PATCH 6/7] Add next pointer to storeEntityValue + +This commit introduces a new nextPtr parameter to storeEntityValue. +After finishing its execution, storeEntityValue function sets this +parameter in way that it points to the next token to process. + +This is useful when we want to leave and reenter storeEntityValue during +its execution since nextPtr will point where we left. + +This commit is base to the following commit. + +Reference: https://github.com/libexpat/libexpat/pull/973/commits/bf97ac508110dc390bd5471ed4904d5a4044332b +Conflict: NA + +--- + lib/xmlparse.c | 20 +++++++++++++------- + 1 file changed, 13 insertions(+), 7 deletions(-) + +diff --git a/lib/xmlparse.c b/lib/xmlparse.c +index c86da91..8f556d9 100644 +--- a/lib/xmlparse.c ++++ b/lib/xmlparse.c +@@ -498,7 +498,8 @@ static int setElementTypePrefix(XML_Parser parser, ELEMENT_TYPE *); + #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); + #else + static enum XML_Error storeSelfEntityValue(XML_Parser parser, ENTITY *entity); + #endif +@@ -4594,7 +4595,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); +@@ -4721,7 +4722,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; + } +@@ -5166,7 +5167,7 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, + // parser->m_declEntity->textPtr. + enum XML_Error result + = storeEntityValue(parser, enc, s + enc->minBytesPerChar, +- next - enc->minBytesPerChar, XML_ACCOUNT_NONE); ++ next - enc->minBytesPerChar, XML_ACCOUNT_NONE, NULL); + if (parser->m_declEntity) { + parser->m_declEntity->textPtr = poolStart(&dtd->entityValuePool); + parser->m_declEntity->textLen +@@ -6324,7 +6325,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; +@@ -6342,8 +6343,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); + +@@ -6412,7 +6414,7 @@ storeEntityValue(XML_Parser parser, const ENCODING *enc, + result = storeEntityValue( + parser, parser->m_internalEncoding, (const char *)entity->textPtr, + (const char *)(entity->textPtr + entity->textLen), +- XML_ACCOUNT_ENTITY_EXPANSION); ++ XML_ACCOUNT_ENTITY_EXPANSION, NULL); + entityTrackingOnClose(parser, entity, __LINE__); + entity->open = XML_FALSE; + if (result) +@@ -6504,6 +6506,10 @@ 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; + } + +-- +2.33.0 + diff --git a/backport-007-CVE-2024-8176.patch b/backport-007-CVE-2024-8176.patch new file mode 100644 index 0000000..4093b9b --- /dev/null +++ b/backport-007-CVE-2024-8176.patch @@ -0,0 +1,249 @@ +From fabae41d28399e1d1623bde9ebd300c962e62ebe Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Berkay=20Eren=20=C3=9Cr=C3=BCn?= +Date: Wed, 18 Sep 2024 13:52:06 +0200 +Subject: [PATCH 7/7] Fix storeEntityValue recursion + +Reference: https://github.com/libexpat/libexpat/pull/973/commits/fabae41d28399e1d1623bde9ebd300c962e62ebe +Conflict: adapt doProlog + +--- + lib/xmlparse.c | 153 ++++++++++++++++++++++++++++++++++++++++++++----- + 1 file changed, 140 insertions(+), 13 deletions(-) + +diff --git a/lib/xmlparse.c b/lib/xmlparse.c +index 8f556d9..6c81713 100644 +--- a/lib/xmlparse.c ++++ b/lib/xmlparse.c +@@ -459,6 +459,10 @@ static enum XML_Error processInternalEntity(XML_Parser parser, ENTITY *entity, + XML_Bool betweenDecl); + static enum XML_Error processAttributeEntity(XML_Parser parser, ENTITY *entity, + XML_Bool betweenDecl); ++#ifdef XML_DTD ++static enum XML_Error processValueEntity(XML_Parser parser, ENTITY *entity, ++ XML_Bool betweenDecl); ++#endif /* XML_DTD */ + static enum XML_Error doContent(XML_Parser parser, int startTagLevel, + const ENCODING *enc, const char *start, + const char *end, const char **endPtr, +@@ -500,6 +504,10 @@ static enum XML_Error storeEntityValue(XML_Parser parser, const ENCODING *enc, + const char *start, const char *end, + 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 +@@ -670,6 +678,8 @@ struct XML_ParserStruct { + 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; +@@ -1109,6 +1119,7 @@ parserCreate(const XML_Char *encodingName, + parser->m_freeTagList = NULL; + parser->m_freeInternalEntities = NULL; + parser->m_freeAttributeEntities = NULL; ++ parser->m_freeValueEntities = NULL; + + parser->m_groupSize = 0; + parser->m_groupConnector = NULL; +@@ -1211,6 +1222,7 @@ parserInit(XML_Parser parser, const XML_Char *encodingName) { + 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; +@@ -1291,6 +1303,15 @@ XML_ParserReset(XML_Parser parser, const XML_Char *encodingName) { + 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) +@@ -1561,6 +1582,20 @@ XML_ParserFree(XML_Parser parser) { + 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); +@@ -5165,9 +5200,9 @@ doProlog(XML_Parser parser, const ENCODING *enc, const char *s, const char *end, + #if defined(XML_DTD) || 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, NULL); ++ 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 +@@ -5951,6 +5986,38 @@ processAttributeEntity(XML_Parser parser, ENTITY *entity, + return XML_ERROR_NONE; + } + ++/* KEEP IN SYNC with processInternalEntity */ ++#ifdef XML_DTD ++static enum XML_Error ++processValueEntity(XML_Parser parser, ENTITY *entity, XML_Bool betweenDecl) { ++ OPEN_INTERNAL_ENTITY *openEntity; ++ ++ if (parser->m_freeValueEntities) { ++ openEntity = parser->m_freeValueEntities; ++ parser->m_freeValueEntities = openEntity->next; ++ } else { ++ openEntity ++ = (OPEN_INTERNAL_ENTITY *)MALLOC(parser, sizeof(OPEN_INTERNAL_ENTITY)); ++ if (! openEntity) ++ return XML_ERROR_NO_MEMORY; ++ } ++ entity->open = XML_TRUE; ++# if XML_GE == 1 ++ entityTrackingOnOpen(parser, entity, __LINE__); ++# endif ++ entity->processed = 0; ++ openEntity->next = parser->m_openValueEntities; ++ parser->m_openValueEntities = openEntity; ++ openEntity->entity = entity; ++ openEntity->startTagLevel = parser->m_tagLevel; ++ openEntity->betweenDecl = betweenDecl; ++ openEntity->internalEventPtr = NULL; ++ openEntity->internalEventEndPtr = NULL; ++ ++ return XML_ERROR_NONE; ++} ++#endif /* XML_DTD */ ++ + static enum XML_Error PTRCALL + internalEntityProcessor(XML_Parser parser, const char *s, const char *end, + const char **nextPtr) { +@@ -6409,16 +6476,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, NULL); +- entityTrackingOnClose(parser, entity, __LINE__); +- entity->open = XML_FALSE; +- if (result) +- goto endEntityValue; ++ result = processValueEntity(parser, entity, XML_FALSE); ++ goto endEntityValue; + } + break; + } +@@ -6513,6 +6572,74 @@ endEntityValue: + 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; ++ 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; ++ } ++ ++# if XML_GE == 1 ++ entityTrackingOnClose(parser, entity, __LINE__); ++# endif ++ entity->open = XML_FALSE; ++ // Remove fully processed openEntity from open entity list. ++ // appendAttributeValue call can add maximum one new entity to ++ // m_openValueEntities since when a new entity is detected, parser ++ // will set the 'reenter' flag and return. Therefore openEntity is either ++ // the head or next to the new head. ++ if (parser->m_openValueEntities == openEntity) { ++ // No new entity detected during entity processing, ++ // openEntity is still the head. ++ parser->m_openValueEntities = parser->m_openValueEntities->next; ++ } else { ++ // New entity detected since list has a new head. openEntity is the ++ // second element. ++ parser->m_openValueEntities->next = openEntity->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; ++} ++ + #else /* XML_GE == 0 */ + + static enum XML_Error +-- +2.33.0 + diff --git a/backport-008-CVE-2024-8176.patch b/backport-008-CVE-2024-8176.patch new file mode 100644 index 0000000..b30f0c7 --- /dev/null +++ b/backport-008-CVE-2024-8176.patch @@ -0,0 +1,229 @@ +From 16a3b9d3566a95c4e502bfd008d05a0d015013ac Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Berkay=20Eren=20=C3=9Cr=C3=BCn?= +Date: Wed, 9 Oct 2024 15:51:05 +0200 +Subject: [PATCH 1/3] Merge entity processors + +Reference: https://github.com/libexpat/libexpat/pull/973/commits/16a3b9d3566a95c4e502bfd008d05a0d015013ac +Conflict: adapt epilogProcessor + +--- + lib/xmlparse.c | 135 +++++++++++++++++++------------------------------ + 1 file changed, 51 insertions(+), 84 deletions(-) + +diff --git a/lib/xmlparse.c b/lib/xmlparse.c +index 6c81713..9d8099e 100644 +--- a/lib/xmlparse.c ++++ b/lib/xmlparse.c +@@ -389,6 +389,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; +@@ -396,6 +402,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 { +@@ -455,14 +462,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 processAttributeEntity(XML_Parser parser, ENTITY *entity, +- XML_Bool betweenDecl); +-#ifdef XML_DTD +-static enum XML_Error processValueEntity(XML_Parser parser, ENTITY *entity, +- XML_Bool betweenDecl); +-#endif /* XML_DTD */ ++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, +@@ -3033,7 +3034,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) { +@@ -5627,7 +5628,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; +@@ -5923,48 +5924,40 @@ epilogProcessor(XML_Parser parser, const char *s, const char *end, + } + } + +-/* KEEP IN SYNC with processAttributeEntity */ + static enum XML_Error +-processInternalEntity(XML_Parser parser, ENTITY *entity, XML_Bool betweenDecl) { +- OPEN_INTERNAL_ENTITY *openEntity; +- +- if (parser->m_freeInternalEntities) { +- openEntity = parser->m_freeInternalEntities; +- parser->m_freeInternalEntities = openEntity->next; +- } else { +- openEntity +- = (OPEN_INTERNAL_ENTITY *)MALLOC(parser, sizeof(OPEN_INTERNAL_ENTITY)); +- if (! openEntity) +- return XML_ERROR_NO_MEMORY; ++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 */ + } +- entity->open = XML_TRUE; +-#if defined(XML_DTD) || XML_GE == 1 +- entityTrackingOnOpen(parser, entity, __LINE__); +-#endif +- entity->processed = 0; +- parser->m_processor = internalEntityProcessor; +- openEntity->next = parser->m_openInternalEntities; +- parser->m_openInternalEntities = openEntity; +- openEntity->entity = entity; +- openEntity->startTagLevel = parser->m_tagLevel; +- openEntity->betweenDecl = betweenDecl; +- openEntity->internalEventPtr = NULL; +- openEntity->internalEventEndPtr = NULL; +- +- triggerReenter(parser); +- return XML_ERROR_NONE; +-} +- +-/* KEEP IN SYNC with processInternalEntity */ +-static enum XML_Error +-processAttributeEntity(XML_Parser parser, ENTITY *entity, +- XML_Bool betweenDecl) { +- OPEN_INTERNAL_ENTITY *openEntity; + +- if (parser->m_freeAttributeEntities) { +- openEntity = parser->m_freeAttributeEntities; +- parser->m_freeAttributeEntities = openEntity->next; +- } else { ++ if (*freeEntityList) { ++ openEntity = *freeEntityList; ++ *freeEntityList = openEntity->next; ++ } else { + openEntity + = (OPEN_INTERNAL_ENTITY *)MALLOC(parser, sizeof(OPEN_INTERNAL_ENTITY)); + if (! openEntity) +@@ -5975,48 +5968,22 @@ processAttributeEntity(XML_Parser parser, ENTITY *entity, + entityTrackingOnOpen(parser, entity, __LINE__); + #endif + entity->processed = 0; +- openEntity->next = parser->m_openAttributeEntities; +- parser->m_openAttributeEntities = 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; + +- return XML_ERROR_NONE; +-} +- +-/* KEEP IN SYNC with processInternalEntity */ +-#ifdef XML_DTD +-static enum XML_Error +-processValueEntity(XML_Parser parser, ENTITY *entity, XML_Bool betweenDecl) { +- OPEN_INTERNAL_ENTITY *openEntity; +- +- if (parser->m_freeValueEntities) { +- openEntity = parser->m_freeValueEntities; +- parser->m_freeValueEntities = openEntity->next; +- } else { +- openEntity +- = (OPEN_INTERNAL_ENTITY *)MALLOC(parser, sizeof(OPEN_INTERNAL_ENTITY)); +- if (! openEntity) +- return XML_ERROR_NO_MEMORY; ++ // 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); + } +- entity->open = XML_TRUE; +-# if XML_GE == 1 +- entityTrackingOnOpen(parser, entity, __LINE__); +-# endif +- entity->processed = 0; +- openEntity->next = parser->m_openValueEntities; +- parser->m_openValueEntities = openEntity; +- openEntity->entity = entity; +- openEntity->startTagLevel = parser->m_tagLevel; +- openEntity->betweenDecl = betweenDecl; +- openEntity->internalEventPtr = NULL; +- openEntity->internalEventEndPtr = NULL; +- + return XML_ERROR_NONE; + } +-#endif /* XML_DTD */ + + static enum XML_Error PTRCALL + internalEntityProcessor(XML_Parser parser, const char *s, const char *end, +@@ -6356,7 +6323,7 @@ appendAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata, + return XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF; + } else { + enum XML_Error result; +- result = processAttributeEntity(parser, entity, XML_FALSE); ++ result = processEntity(parser, entity, XML_FALSE, ENTITY_ATTRIBUTE); + if (nextPtr) { + *nextPtr = next; + } +@@ -6476,7 +6443,7 @@ storeEntityValue(XML_Parser parser, const ENCODING *enc, + } else + dtd->keepProcessing = dtd->standalone; + } else { +- result = processValueEntity(parser, entity, XML_FALSE); ++ result = processEntity(parser, entity, XML_FALSE, ENTITY_VALUE); + goto endEntityValue; + } + break; +-- +2.33.0 + diff --git a/backport-009-CVE-2024-8176.patch b/backport-009-CVE-2024-8176.patch new file mode 100644 index 0000000..7170b13 --- /dev/null +++ b/backport-009-CVE-2024-8176.patch @@ -0,0 +1,199 @@ +From 08ff7591ffe9519f627e246f841427c6f10edc2a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Berkay=20Eren=20=C3=9Cr=C3=BCn?= +Date: Tue, 15 Oct 2024 11:37:09 +0200 +Subject: [PATCH] Fix entity debug order + +The fix for entity processing changes the order of opening and closing +of entities. The reason is the new iterative approach that closes +and removes entities as soon as they are fully processed, unlike +the recursive approach that, as a side effect of the recursion, waits +the inner entities before closing and removal. + +This commit delays the removal and the call to entityTrackingOnClose +until the current entities inner entities are processed, which in turn +allows us to have the correct debug order again. + +Reference: https://github.com/libexpat/libexpat/pull/973/commits/c20ce3aaa3a114e7d671d11625a63d75de904e31 +Conflict: adapt internalEntityProcessor +--- + lib/xmlparse.c | 121 ++++++++++++++++++++++++++----------------------- + 1 file changed, 64 insertions(+), 57 deletions(-) + +diff --git a/lib/xmlparse.c b/lib/xmlparse.c +index 9dda309..325d5e7 100644 +--- a/lib/xmlparse.c ++++ b/lib/xmlparse.c +@@ -6004,38 +6004,46 @@ 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->open) { ++ 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; + + #ifdef XML_DTD +- 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 ++ 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 { + #endif /* XML_DTD */ +- result = doContent(parser, openEntity->startTagLevel, +- parser->m_internalEncoding, textStart, textEnd, &next, +- XML_FALSE, XML_ACCOUNT_ENTITY_EXPANSION); ++ result = doContent(parser, openEntity->startTagLevel, ++ parser->m_internalEncoding, textStart, textEnd, &next, ++ XML_FALSE, XML_ACCOUNT_ENTITY_EXPANSION); ++ } + +- if (result != XML_ERROR_NONE) +- return result; +- 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); ++ 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; ++ } ++ ++ entity->open = XML_FALSE; ++ triggerReenter(parser); + return result; + } + + #if defined(XML_DTD) || XML_GE == 1 + entityTrackingOnClose(parser, entity, __LINE__); + #endif +- entity->open = XML_FALSE; + // Remove fully processed openEntity from open entity list. doContent call can + // add maximum one new entity to m_openInternalEntities since when a new + // entity is detected, parser will go into REENTER state and return. Therefore +@@ -6053,19 +6061,6 @@ internalEntityProcessor(XML_Parser parser, const char *s, const char *end, + openEntity->next = parser->m_freeInternalEntities; + parser->m_freeInternalEntities = openEntity; + +- // If the state is XML_SUSPENDED and 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 the state is XML_PARSING, m_reenter is set, and there are more open +- // entities, we want to return and reenter to internalEntityProcessor to +- // process the next entity in the list +- if (parser->m_openInternalEntities != NULL +- && (parser->m_parsingStatus.parsing == XML_SUSPENDED +- || (parser->m_parsingStatus.parsing == XML_PARSING +- && parser->m_reenter))) { +- return XML_ERROR_NONE; +- } +- + if (parser->m_openInternalEntities == NULL) { + parser->m_processor = entity->is_param ? prologProcessor : contentProcessor; + // internalEntityProcessor is called from callProcessor's while(1) loop, +@@ -6113,23 +6108,29 @@ storeAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata, + = (const char *)(entity->textPtr + entity->textLen); + /* Set a safe default value in case 'next' does not get set */ + const char *nextInEntity = textStart; +- 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); ++ if (entity->open) { ++ 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->open = XML_FALSE; ++ triggerReenter(parser); + continue; + } + + #if XML_GE == 1 + entityTrackingOnClose(parser, entity, __LINE__); + #endif +- entity->open = XML_FALSE; + // Remove fully processed openEntity from open entity list. + // appendAttributeValue call can add maximum one new entity to + // m_openAttributeEntities since when a new entity is detected, parser +@@ -6566,23 +6567,29 @@ callStoreEntityValue(XML_Parser parser, const ENCODING *enc, + = (const char *)(entity->textPtr + entity->textLen); + /* Set a safe default value in case 'next' does not get set */ + const char *nextInEntity = textStart; +- 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); ++ if (entity->open) { ++ 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->open = XML_FALSE; ++ triggerReenter(parser); + continue; + } + + # if XML_GE == 1 + entityTrackingOnClose(parser, entity, __LINE__); + # endif +- entity->open = XML_FALSE; + // Remove fully processed openEntity from open entity list. + // appendAttributeValue call can add maximum one new entity to + // m_openValueEntities since when a new entity is detected, parser +-- +2.33.0 + diff --git a/backport-010-CVE-2024-8176.patch b/backport-010-CVE-2024-8176.patch new file mode 100644 index 0000000..41845bc --- /dev/null +++ b/backport-010-CVE-2024-8176.patch @@ -0,0 +1,133 @@ +From 4d0be4b56cf61428ccb17fbf9953a39defb77282 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Berkay=20Eren=20=C3=9Cr=C3=BCn?= +Date: Sat, 23 Nov 2024 21:37:15 +0100 +Subject: [PATCH] Fix infinite loop with indirectly recursive entities + +Detection of recursive entity references are currently failing because +we process and close entities before their inner references are +processed. Since the detection works by checking wheter the referenced +entity is already open, this early close leads to wrong results. This +commit delays closing entities until their inner entities are processed +and closed. This is achieved by postponing the unsetting of the open +flag and using a new hasMore flag to check if the entity has more +elements to process. + +Reference: https://github.com/libexpat/libexpat/pull/973/commits/495fb53b16d510dd0d0307fef2145437a7ad6ab1 +Conflict: adapt internalEntityProcessor +--- + lib/xmlparse.c | 29 +++++++++++++++++++++++------ + 1 file changed, 23 insertions(+), 6 deletions(-) + +diff --git a/lib/xmlparse.c b/lib/xmlparse.c +index 325d5e7..bad6aae 100644 +--- a/lib/xmlparse.c ++++ b/lib/xmlparse.c +@@ -303,6 +303,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; +@@ -5970,6 +5974,7 @@ processEntity(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 +@@ -6004,7 +6009,7 @@ internalEntityProcessor(XML_Parser parser, const char *s, const char *end, + return XML_ERROR_UNEXPECTED_STATE; + + entity = openEntity->entity; +- if (entity->open) { ++ 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 */ +@@ -6036,7 +6041,10 @@ internalEntityProcessor(XML_Parser parser, const char *s, const char *end, + return result; + } + +- entity->open = XML_FALSE; ++ // 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; + } +@@ -6044,6 +6052,7 @@ internalEntityProcessor(XML_Parser parser, const char *s, const char *end, + #if defined(XML_DTD) || XML_GE == 1 + entityTrackingOnClose(parser, entity, __LINE__); + #endif ++ entity->open = XML_FALSE; + // Remove fully processed openEntity from open entity list. doContent call can + // add maximum one new entity to m_openInternalEntities since when a new + // entity is detected, parser will go into REENTER state and return. Therefore +@@ -6108,7 +6117,7 @@ storeAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata, + = (const char *)(entity->textPtr + entity->textLen); + /* Set a safe default value in case 'next' does not get set */ + const char *nextInEntity = textStart; +- if (entity->open) { ++ if (entity->hasMore) { + result = appendAttributeValue( + parser, parser->m_internalEncoding, isCdata, textStart, textEnd, + pool, XML_ACCOUNT_ENTITY_EXPANSION, &nextInEntity); +@@ -6123,7 +6132,10 @@ storeAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata, + continue; + } + +- entity->open = XML_FALSE; ++ // 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; + triggerReenter(parser); + continue; + } +@@ -6131,6 +6143,7 @@ storeAttributeValue(XML_Parser parser, const ENCODING *enc, XML_Bool isCdata, + #if XML_GE == 1 + entityTrackingOnClose(parser, entity, __LINE__); + #endif ++ entity->open = XML_FALSE; + // Remove fully processed openEntity from open entity list. + // appendAttributeValue call can add maximum one new entity to + // m_openAttributeEntities since when a new entity is detected, parser +@@ -6567,7 +6580,7 @@ callStoreEntityValue(XML_Parser parser, const ENCODING *enc, + = (const char *)(entity->textPtr + entity->textLen); + /* Set a safe default value in case 'next' does not get set */ + const char *nextInEntity = textStart; +- if (entity->open) { ++ if (entity->hasMore) { + result = storeEntityValue(parser, parser->m_internalEncoding, textStart, + textEnd, XML_ACCOUNT_ENTITY_EXPANSION, + &nextInEntity); +@@ -6582,7 +6595,10 @@ callStoreEntityValue(XML_Parser parser, const ENCODING *enc, + continue; + } + +- entity->open = XML_FALSE; ++ // 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; + triggerReenter(parser); + continue; + } +@@ -6590,6 +6606,7 @@ callStoreEntityValue(XML_Parser parser, const ENCODING *enc, + # if XML_GE == 1 + entityTrackingOnClose(parser, entity, __LINE__); + # endif ++ entity->open = XML_FALSE; + // Remove fully processed openEntity from open entity list. + // appendAttributeValue call can add maximum one new entity to + // m_openValueEntities since when a new entity is detected, parser +-- +2.33.0 + diff --git a/backport-011-CVE-2024-8176.patch b/backport-011-CVE-2024-8176.patch new file mode 100644 index 0000000..73c0e82 --- /dev/null +++ b/backport-011-CVE-2024-8176.patch @@ -0,0 +1,53 @@ +From f2edeaaecebfad1edef3e6504ffb772e5e4dd089 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Berkay=20Eren=20=C3=9Cr=C3=BCn?= +Date: Wed, 5 Mar 2025 12:33:47 +0100 +Subject: [PATCH] Delete the check that prevents reentry + +The early return in case of zero open internal entities and matching +end/nextPtr pointers cause the parser to miss XML_ERROR_NO_ELEMENTS +error. + +The reason is that the internalEntityProcessor does not set the +m_reenter flag in such a case, which results in skipping the +prologProcessor or contentProcessor depending on wheter is_param is set +or not. However, this last skipped call to mentioned processors can +detect the non-existence of elements when some are expected. + +Reference: https://github.com/libexpat/libexpat/pull/973/commits/f2edeaaecebfad1edef3e6504ffb772e5e4dd089 +Conflict: NA + +--- + lib/xmlparse.c | 10 ++-------- + 1 file changed, 2 insertions(+), 8 deletions(-) + +diff --git a/lib/xmlparse.c b/lib/xmlparse.c +index d5161e93..de459192 100644 +--- a/lib/xmlparse.c ++++ b/ib/xmlparse.c +@@ -6034,6 +6034,8 @@ 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; +@@ -6101,14 +6103,6 @@ internalEntityProcessor(XML_Parser parser, const char *s, const char *end, + + if (parser->m_openInternalEntities == NULL) { + parser->m_processor = entity->is_param ? prologProcessor : contentProcessor; +- // internalEntityProcessor is called from callProcessor's while(1) loop, +- // therefore "end" denotes callProcessor's "end", which denotes the end +- // of the current buffer being parsed. Consequently, if we do not have +- // any open entities left and have reached to the end, we must not +- // trigger a reentry. +- if (end == *nextPtr) { +- return XML_ERROR_NONE; +- } + } + triggerReenter(parser); + return XML_ERROR_NONE; +-- +2.33.0 + diff --git a/expat.spec b/expat.spec index b8de74f..624fb1d 100644 --- a/expat.spec +++ b/expat.spec @@ -1,7 +1,7 @@ %define Rversion %(echo %{version} | sed -e 's/\\./_/g' -e 's/^/R_/') Name: expat Version: 2.5.0 -Release: 7 +Release: 8 Summary: An XML parser library License: MIT URL: https://libexpat.github.io/ @@ -31,6 +31,17 @@ Patch21: backport-CVE-2024-45491.patch Patch22: backport-CVE-2024-45492.patch Patch23: backport-CVE-2024-50602.patch Patch24: backport-CVE-2024-50602-testcase.patch +Patch25: backport-001-CVE-2024-8176.patch +Patch26: backport-002-CVE-2024-8176.patch +Patch27: backport-003-CVE-2024-8176.patch +Patch28: backport-004-CVE-2024-8176.patch +Patch29: backport-005-CVE-2024-8176.patch +Patch30: backport-006-CVE-2024-8176.patch +Patch31: backport-007-CVE-2024-8176.patch +Patch32: backport-008-CVE-2024-8176.patch +Patch33: backport-009-CVE-2024-8176.patch +Patch34: backport-010-CVE-2024-8176.patch +Patch35: backport-011-CVE-2024-8176.patch BuildRequires: sed,autoconf,automake,gcc-c++,libtool,xmlto @@ -79,6 +90,9 @@ find %{buildroot} -type f -name changelog -delete %{_mandir}/man1/* %changelog +* Fri Mar 28 2025 zhuofeng <1107893276@qq.com> - 2.5.0-8 +- fix CVE-2024-8176 + * Tue Oct 29 2024 liningjie - 2.5.0-7 - add testcase for CVE-2024-50602