From a2d50d27baad0ba1bbef0a146d276b0bf14b03e0 Mon Sep 17 00:00:00 2001 From: Azure Linux Security Servicing Account Date: Fri, 21 Aug 2026 10:54:36 +0000 Subject: [PATCH] Patch expat for CVE-2026-76957, CVE-2026-76956, CVE-2026-76641, CVE-2026-66046 --- SPECS/expat/CVE-2026-66046.patch | 199 +++++++++++++++++ SPECS/expat/CVE-2026-76641.patch | 162 ++++++++++++++ SPECS/expat/CVE-2026-76956.patch | 28 +++ SPECS/expat/CVE-2026-76957.patch | 208 ++++++++++++++++++ SPECS/expat/expat.spec | 9 +- .../manifests/package/pkggen_core_aarch64.txt | 6 +- .../manifests/package/pkggen_core_x86_64.txt | 6 +- .../manifests/package/toolchain_aarch64.txt | 8 +- .../manifests/package/toolchain_x86_64.txt | 8 +- 9 files changed, 619 insertions(+), 15 deletions(-) create mode 100644 SPECS/expat/CVE-2026-66046.patch create mode 100644 SPECS/expat/CVE-2026-76641.patch create mode 100644 SPECS/expat/CVE-2026-76956.patch create mode 100644 SPECS/expat/CVE-2026-76957.patch diff --git a/SPECS/expat/CVE-2026-66046.patch b/SPECS/expat/CVE-2026-66046.patch new file mode 100644 index 00000000000..bb68c9e7de8 --- /dev/null +++ b/SPECS/expat/CVE-2026-66046.patch @@ -0,0 +1,199 @@ +From 708c61bcbf22219cc55b791c02869e1197ff7f97 Mon Sep 17 00:00:00 2001 +From: Sebastian Pipping +Date: Thu, 13 Aug 2026 15:47:24 +0200 +Subject: [PATCH 1/2] lib: Rename hash table `defaultAttsNames` to + `defaultAttForName` + +It was previously used as a "set". This prepares for the upcoming +change to a true "dictionary". +--- + lib/xmlparse.c | 24 ++++++++++++------------ + 1 file changed, 12 insertions(+), 12 deletions(-) + +diff --git a/lib/xmlparse.c b/lib/xmlparse.c +index ac79b9c..36105f8 100644 +--- a/lib/xmlparse.c ++++ b/lib/xmlparse.c +@@ -394,7 +394,7 @@ typedef struct { + size_t nDefaultAtts; + size_t allocDefaultAtts; + DEFAULT_ATTRIBUTE *defaultAtts; +- HASH_TABLE defaultAttsNames; ++ HASH_TABLE defaultAttForName; + } ELEMENT_TYPE; + + typedef struct { +@@ -3837,8 +3837,8 @@ storeAtts(XML_Parser parser, const ENCODING *enc, const char *attStr, + sizeof(ELEMENT_TYPE)); + if (! elementType) + return XML_ERROR_NO_MEMORY; +- if (! elementType->defaultAttsNames.parser) +- hashTableInit(&(elementType->defaultAttsNames), parser); ++ if (! elementType->defaultAttForName.parser) ++ hashTableInit(&(elementType->defaultAttForName), parser); + if (parser->m_ns && ! setElementTypePrefix(parser, elementType)) + return XML_ERROR_NO_MEMORY; + } +@@ -7239,7 +7239,7 @@ defineAttribute(ELEMENT_TYPE *type, ATTRIBUTE_ID *attId, XML_Bool isCdata, + /* The handling of default attributes gets messed up if we have + a default which duplicates a non-default. */ + NAMED *const nameFound +- = lookup(parser, &(type->defaultAttsNames), attId->name, 0); ++ = lookup(parser, &(type->defaultAttForName), attId->name, 0); + if (nameFound) + return 1; + if (isId && ! type->idAtt && ! attId->xmlns) +@@ -7276,7 +7276,7 @@ defineAttribute(ELEMENT_TYPE *type, ATTRIBUTE_ID *attId, XML_Bool isCdata, + attId->maybeTokenized = XML_TRUE; + + NAMED *const nameAddedOrFound +- = lookup(parser, &(type->defaultAttsNames), attId->name, sizeof(NAMED)); ++ = lookup(parser, &(type->defaultAttForName), attId->name, sizeof(NAMED)); + if (! nameAddedOrFound) + return 0; + +@@ -7597,7 +7597,7 @@ dtdReset(DTD *p, XML_Parser parser) { + ELEMENT_TYPE *e = (ELEMENT_TYPE *)hashTableIterNext(&iter); + if (! e) + break; +- hashTableDestroy(&(e->defaultAttsNames)); ++ hashTableDestroy(&(e->defaultAttForName)); + FREE(parser, e->defaultAtts); + } + hashTableClear(&(p->generalEntities)); +@@ -7639,7 +7639,7 @@ dtdDestroy(DTD *p, XML_Bool isDocEntity, XML_Parser parser) { + ELEMENT_TYPE *e = (ELEMENT_TYPE *)hashTableIterNext(&iter); + if (! e) + break; +- hashTableDestroy(&(e->defaultAttsNames)); ++ hashTableDestroy(&(e->defaultAttForName)); + FREE(parser, e->defaultAtts); + } + hashTableDestroy(&(p->generalEntities)); +@@ -7732,8 +7732,8 @@ dtdCopy(XML_Parser oldParser, DTD *newDtd, const DTD *oldDtd, + if (! newE) + return 0; + +- if (! newE->defaultAttsNames.parser) +- hashTableInit(&(newE->defaultAttsNames), parser); ++ if (! newE->defaultAttForName.parser) ++ hashTableInit(&(newE->defaultAttForName), parser); + + if (oldE->nDefaultAtts) { + /* Detect and prevent integer overflow. */ +@@ -7766,7 +7766,7 @@ dtdCopy(XML_Parser oldParser, DTD *newDtd, const DTD *oldDtd, + } else + newE->defaultAtts[i].value = NULL; + +- NAMED *const nameAddedOrFound = lookup(parser, &(newE->defaultAttsNames), ++ NAMED *const nameAddedOrFound = lookup(parser, &(newE->defaultAttForName), + attributeName, sizeof(NAMED)); + if (! nameAddedOrFound) { + return 0; +@@ -8535,8 +8535,8 @@ getElementType(XML_Parser parser, const ENCODING *enc, const char *ptr, + sizeof(ELEMENT_TYPE)); + if (! ret) + return NULL; +- if (! ret->defaultAttsNames.parser) +- hashTableInit(&(ret->defaultAttsNames), getRootParserOf(parser, NULL)); ++ if (! ret->defaultAttForName.parser) ++ hashTableInit(&(ret->defaultAttForName), getRootParserOf(parser, NULL)); + if (ret->name != name) + poolDiscard(&dtd->pool); + else { +-- +2.45.4 + + +From e8fbef3543aecddd6aada3be013dc6123d08ed48 Mon Sep 17 00:00:00 2001 +From: Sebastian Pipping +Date: Thu, 13 Aug 2026 16:39:35 +0200 +Subject: [PATCH 2/2] lib: Migrate .isCdata lookup from a linear loop to a hash + table lookup + +.. to resolve quadratic runtime + +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: https://github.com/libexpat/libexpat/pull/1321.patch +--- + lib/xmlparse.c | 48 ++++++++++++++++++++++++++++++++++++++++-------- + 1 file changed, 40 insertions(+), 8 deletions(-) + +diff --git a/lib/xmlparse.c b/lib/xmlparse.c +index 36105f8..0865d4b 100644 +--- a/lib/xmlparse.c ++++ b/lib/xmlparse.c +@@ -381,6 +381,22 @@ typedef struct { + const XML_Char *value; + } DEFAULT_ATTRIBUTE; + ++// This structure allows mapping attribute names to instances of ++// `DEFAULT_ATTRIBUTE`. ++typedef struct { ++ // Member `name` goes first to make this structure compatible with structure ++ // `NAMED` (further up), which is needed to support use of structure ++ // `NAME_AND_DEFAULT_ATTRIBUTE` in a hash table as implemented by function ++ // `lookup` (further down). ++ const XML_Char *name; ++ // We would store a `DEFAULT_ATTRIBUTE *` here but the backing array ++ // can be reallocated which would invalidate the pointer. Using an index ++ // into the array instead, avoids that problem. ++ size_t attIndex; ++ // This is set to `false` by function `lookup`. ++ bool initialized; ++} NAME_AND_DEFAULT_ATTRIBUTE; ++ + typedef struct { + unsigned long version; + unsigned long hash; +@@ -3951,11 +3967,14 @@ storeAtts(XML_Parser parser, const ENCODING *enc, const char *attStr, + + /* figure out whether declared as other than CDATA */ + if (attId->maybeTokenized) { +- for (size_t j = 0; j < nDefaultAtts; j++) { +- if (attId == elementType->defaultAtts[j].id) { +- isCdata = elementType->defaultAtts[j].isCdata; +- break; +- } ++ NAME_AND_DEFAULT_ATTRIBUTE *const nameAndDefaultAttribute ++ = (NAME_AND_DEFAULT_ATTRIBUTE *)lookup( ++ parser, &(elementType->defaultAttForName), attId->name, 0); ++ if (nameAndDefaultAttribute != NULL) { ++ assert(nameAndDefaultAttribute->attIndex < elementType->nDefaultAtts); ++ const DEFAULT_ATTRIBUTE *const att ++ = elementType->defaultAtts + nameAndDefaultAttribute->attIndex; ++ isCdata = att->isCdata; + } + } + +@@ -7275,11 +7294,24 @@ defineAttribute(ELEMENT_TYPE *type, ATTRIBUTE_ID *attId, XML_Bool isCdata, + if (! isCdata) + attId->maybeTokenized = XML_TRUE; + +- NAMED *const nameAddedOrFound +- = lookup(parser, &(type->defaultAttForName), attId->name, sizeof(NAMED)); +- if (! nameAddedOrFound) ++ NAME_AND_DEFAULT_ATTRIBUTE *const nameAndDefaultAttribute ++ = (NAME_AND_DEFAULT_ATTRIBUTE *)lookup( ++ parser, &(type->defaultAttForName), attId->name, ++ sizeof(NAME_AND_DEFAULT_ATTRIBUTE)); ++ if (! nameAndDefaultAttribute) + return 0; + ++ assert(nameAndDefaultAttribute->name == attId->name); ++ ++ // NOTE: The XML 1.0r4 spec says: ++ // "When more than one definition is provided for the same attribute of a ++ // given element type, the first declaration is binding and later ++ // declarations are ignored." ++ if (! nameAndDefaultAttribute->initialized) { ++ nameAndDefaultAttribute->attIndex = type->nDefaultAtts; ++ nameAndDefaultAttribute->initialized = true; ++ } ++ + type->nDefaultAtts += 1; + return 1; + } +-- +2.45.4 + diff --git a/SPECS/expat/CVE-2026-76641.patch b/SPECS/expat/CVE-2026-76641.patch new file mode 100644 index 00000000000..80a326985a9 --- /dev/null +++ b/SPECS/expat/CVE-2026-76641.patch @@ -0,0 +1,162 @@ +From f3c587cc0228957f897294ad6d6728d14528aa3b Mon Sep 17 00:00:00 2001 +From: Zeyou Liu +Date: Thu, 20 Aug 2026 20:29:56 +0800 +Subject: [PATCH] lib: Fix out-of-bounds read from hash table entries created + by dtdCopy + +Commit f8f7c4ff grew the entries of ELEMENT_TYPE member +.defaultAttForName from structure NAMED to the larger structure +NAME_AND_DEFAULT_ATTRIBUTE and adjusted function defineAttribute +accordingly, but function dtdCopy kept creating entries of size +sizeof(NAMED). Because function lookup allocates exactly createSize +bytes, function storeAtts reads member .attIndex past the end of those +entries whenever attributes are parsed by a parser that was created by +XML_ExternalEntityParserCreate. + +That out-of-bounds value is then used as an index into member +.defaultAtts, so the effects range from silently not normalizing +whitespace in attributes that are not of type CDATA, to dereferencing a +wild pointer: a release build of master segfaults in function storeAtts +on the document used by the new test. A zero-filled heap happens to +yield index 0, which is why the existing tests did not catch this. + +Member .attIndex is now stored the way function defineAttribute stores +it, i.e. keeping the index of the first declaration, so that a copied +DTD resolves attributes exactly like the DTD that it was copied from. + +This was found while backporting the fix for CVE-2026-66046 onto Expat +2.6.4 for the OpenCloudOS Stream distribution. Only master is affected, +no released version of Expat contains commit f8f7c4ff. + +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: https://github.com/libexpat/libexpat/pull/1331.patch +--- + lib/xmlparse.c | 17 +++++++++-- + tests/basic_tests.c | 74 +++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 88 insertions(+), 3 deletions(-) + +diff --git a/lib/xmlparse.c b/lib/xmlparse.c +index 0865d4b..828fba9 100644 +--- a/lib/xmlparse.c ++++ b/lib/xmlparse.c +@@ -7798,11 +7798,22 @@ dtdCopy(XML_Parser oldParser, DTD *newDtd, const DTD *oldDtd, + } else + newE->defaultAtts[i].value = NULL; + +- NAMED *const nameAddedOrFound = lookup(parser, &(newE->defaultAttForName), +- attributeName, sizeof(NAMED)); +- if (! nameAddedOrFound) { ++ NAME_AND_DEFAULT_ATTRIBUTE *const nameAndDefaultAttribute ++ = (NAME_AND_DEFAULT_ATTRIBUTE *)lookup( ++ parser, &(newE->defaultAttForName), attributeName, ++ sizeof(NAME_AND_DEFAULT_ATTRIBUTE)); ++ if (! nameAndDefaultAttribute) { + return 0; + } ++ ++ // NOTE: The XML 1.0r4 spec says: ++ // "When more than one definition is provided for the same attribute of a ++ // given element type, the first declaration is binding and later ++ // declarations are ignored." ++ if (! nameAndDefaultAttribute->initialized) { ++ nameAndDefaultAttribute->attIndex = i; ++ nameAndDefaultAttribute->initialized = true; ++ } + } + } + +diff --git a/tests/basic_tests.c b/tests/basic_tests.c +index 308adf6..6c2d328 100644 +--- a/tests/basic_tests.c ++++ b/tests/basic_tests.c +@@ -2809,6 +2809,79 @@ START_TEST(test_duplicate_id_attribute_multiple_attlistdecl) { + } + END_TEST + ++static void XMLCALL ++check_second_attr_normalization(void *userData, const XML_Char *name, ++ const XML_Char **atts) { ++ int *const seen_second = userData; ++ UNUSED_P(name); ++ ++ for (size_t i = 0; atts[i] != NULL; i += 2) { ++ const XML_Char *const key = atts[i]; ++ const XML_Char *const value = atts[i + 1]; ++ if (xcstrcmp(key, XCS("second")) != 0) ++ continue; ++ *seen_second = 1; ++ /* Attribute "second" is not of type CDATA, so leading, trailing and ++ * repeated whitespace is to be normalized away. */ ++ if (xcstrcmp(value, XCS("a b")) != 0) ++ fail("Attribute of non-CDATA type was not whitespace-normalized"); ++ } ++} ++ ++static int XMLCALL ++external_entity_attr_checker(XML_Parser parser, const XML_Char *context, ++ const XML_Char *base, const XML_Char *systemId, ++ const XML_Char *publicId) { ++ const char *const text = ""; ++ UNUSED_P(base); ++ UNUSED_P(systemId); ++ UNUSED_P(publicId); ++ ++ XML_Parser ext_parser = XML_ExternalEntityParserCreate(parser, context, NULL); ++ if (ext_parser == NULL) ++ fail("Could not create external entity parser"); ++ ++ if (_XML_Parse_SINGLE_BYTES(ext_parser, text, (int)strlen(text), XML_TRUE) ++ != XML_STATUS_OK) ++ xml_failure(ext_parser); ++ ++ XML_ParserFree(ext_parser); ++ return XML_STATUS_OK; ++} ++ ++START_TEST(test_default_attr_index_after_dtd_copy) { ++ /* Function storeAtts resolves member .attIndex of structure ++ * NAME_AND_DEFAULT_ATTRIBUTE to tell whether an attribute value needs ++ * whitespace normalization, so function dtdCopy needs to carry that index ++ * over to the copy. Attribute "first" is declared before attribute ++ * "second" so that a mixed-up index resolves to the wrong declaration. ++ */ ++ const char *text = "\n" ++ " \n" ++ " \n" ++ " \n" ++ " \n" ++ "]>\n" ++ "&e;\n"; ++ int seen_second = 0; ++ ++ XML_Parser parser = XML_ParserCreate(NULL); ++ assert_true(parser != NULL); ++ XML_SetUserData(parser, &seen_second); ++ XML_SetExternalEntityRefHandler(parser, external_entity_attr_checker); ++ XML_SetStartElementHandler(parser, check_second_attr_normalization); ++ ++ if (_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE) ++ != XML_STATUS_OK) ++ xml_failure(parser); ++ if (! seen_second) ++ fail("Attribute \"second\" has not been reported"); ++ ++ XML_ParserFree(parser); ++} ++END_TEST ++ + /* Test reset works correctly in the middle of processing an internal + * entity. Exercises some obscure code in XML_ParserReset(). + */ +@@ -6737,6 +6810,7 @@ make_basic_test_case(Suite *s) { + tcase_add_test(tc_basic, + test_duplicate_cdata_attribute_multiple_attlistdecl_3); + tcase_add_test(tc_basic, test_duplicate_id_attribute_multiple_attlistdecl); ++ tcase_add_test__if_xml_ge(tc_basic, test_default_attr_index_after_dtd_copy); + tcase_add_test__if_xml_ge(tc_basic, test_reset_in_entity); + tcase_add_test(tc_basic, test_resume_invalid_parse); + tcase_add_test(tc_basic, test_resume_resuspended); +-- +2.45.4 + diff --git a/SPECS/expat/CVE-2026-76956.patch b/SPECS/expat/CVE-2026-76956.patch new file mode 100644 index 00000000000..e4cb59871ac --- /dev/null +++ b/SPECS/expat/CVE-2026-76956.patch @@ -0,0 +1,28 @@ +From 43364f5316d0b5e18ca098f06d3ea13e37685cf5 Mon Sep 17 00:00:00 2001 +From: Sorrachat <32319737+Sorrashut-K@users.noreply.github.com> +Date: Fri, 14 Aug 2026 17:29:50 -0400 +Subject: [PATCH] lib: Fix inverted getentropy() return in + writeRandomBytes_getentropy + +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: https://github.com/libexpat/libexpat/pull/1326.patch +--- + lib/random_getentropy.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/lib/random_getentropy.c b/lib/random_getentropy.c +index d258df6..ad8b198 100644 +--- a/lib/random_getentropy.c ++++ b/lib/random_getentropy.c +@@ -54,7 +54,7 @@ + bool + writeRandomBytes_getentropy(void *target, size_t count) { + errno = 0; +- const bool success = getentropy(target, count); ++ const bool success = (getentropy(target, count) == 0); + // MSan does not understand `getentropy`, so explain its effects + if (success) + MSAN_UNPOISON(target, count); +-- +2.45.4 + diff --git a/SPECS/expat/CVE-2026-76957.patch b/SPECS/expat/CVE-2026-76957.patch new file mode 100644 index 00000000000..145ff9c5946 --- /dev/null +++ b/SPECS/expat/CVE-2026-76957.patch @@ -0,0 +1,208 @@ +From 11e76f24d473322a3c572d4aac4489aff73d4704 Mon Sep 17 00:00:00 2001 +From: Darren Carreras +Date: Mon, 17 Aug 2026 21:16:00 -0400 +Subject: [PATCH 1/2] Protect custom encoding callbacks from parser reentry + +Co-authored-by: Sebastian Pipping +--- + lib/xmlparse.c | 49 ++++++++++++++++++++++++++++++++++++++++--------- + 1 file changed, 40 insertions(+), 9 deletions(-) + +diff --git a/lib/xmlparse.c b/lib/xmlparse.c +index 828fba9..cb8839b 100644 +--- a/lib/xmlparse.c ++++ b/lib/xmlparse.c +@@ -771,6 +771,8 @@ struct XML_ParserStruct { + void *m_unknownEncodingMem; + void *m_unknownEncodingData; + void *m_unknownEncodingHandlerData; ++ // Application callback invoked by callUnknownEncodingConvert. ++ int(XMLCALL *m_unknownEncodingConvert)(void *, const char *); + void(XMLCALL *m_unknownEncodingRelease)(void *); + PROLOG_STATE m_prologState; + Processor *m_processor; +@@ -1193,6 +1195,25 @@ isCalledFromInsideHandler(XML_Parser parser) { + return parser->m_handlerCallDepth > 0; + } + ++static void ++callUnknownEncodingRelease(XML_Parser parser) { ++ beforeHandler(parser); ++ parser->m_unknownEncodingRelease(parser->m_unknownEncodingData); ++ afterHandler(parser); ++ parser->m_unknownEncodingRelease = NULL; ++ parser->m_unknownEncodingData = NULL; ++} ++ ++static int XMLCALL ++callUnknownEncodingConvert(void *data, const char *p) { ++ XML_Parser parser = data; ++ beforeHandler(parser); ++ const int result ++ = parser->m_unknownEncodingConvert(parser->m_unknownEncodingData, p); ++ afterHandler(parser); ++ return result; ++} ++ + static enum XML_Error + callProcessor(XML_Parser parser, const char *start, const char *end, + const char **endPtr) { +@@ -1540,6 +1561,7 @@ parserInit(XML_Parser parser, const XML_Char *encodingName) { + parser->m_inheritedBindings = NULL; + parser->m_nSpecifiedAtts = 0; + parser->m_unknownEncodingMem = NULL; ++ parser->m_unknownEncodingConvert = NULL; + parser->m_unknownEncodingRelease = NULL; + parser->m_unknownEncodingData = NULL; + parser->m_parsingStatus.parsing = XML_INITIALIZED; +@@ -1620,7 +1642,7 @@ XML_ParserReset(XML_Parser parser, const XML_Char *encodingName) { + moveToFreeBindingList(parser, parser->m_inheritedBindings); + FREE(parser, parser->m_unknownEncodingMem); + if (parser->m_unknownEncodingRelease) +- parser->m_unknownEncodingRelease(parser->m_unknownEncodingData); ++ callUnknownEncodingRelease(parser); + poolClear(&parser->m_tempPool); + poolClear(&parser->m_temp2Pool); + FREE(parser, (void *)parser->m_protocolEncodingName); +@@ -1931,7 +1953,7 @@ XML_ParserFree(XML_Parser parser) { + FREE(parser, parser->m_nsAtts); + FREE(parser, parser->m_unknownEncodingMem); + if (parser->m_unknownEncodingRelease) +- parser->m_unknownEncodingRelease(parser->m_unknownEncodingData); ++ callUnknownEncodingRelease(parser); + FREE(parser, parser); + } + +@@ -4965,25 +4987,34 @@ handleUnknownEncoding(XML_Parser parser, const XML_Char *encodingName) { + const int status = parser->m_unknownEncodingHandler( + parser->m_unknownEncodingHandlerData, encodingName, &info); + afterHandler(parser); ++ ++ parser->m_unknownEncodingRelease = info.release; ++ parser->m_unknownEncodingData = info.data; ++ + if (status) { + ENCODING *enc; + parser->m_unknownEncodingMem = MALLOC(parser, XmlSizeOfUnknownEncoding()); + if (! parser->m_unknownEncodingMem) { +- if (info.release) +- info.release(info.data); ++ if (parser->m_unknownEncodingRelease) ++ callUnknownEncodingRelease(parser); ++ else ++ parser->m_unknownEncodingData = NULL; + return XML_ERROR_NO_MEMORY; + } ++ parser->m_unknownEncodingConvert = info.convert; + enc = (parser->m_ns ? XmlInitUnknownEncodingNS : XmlInitUnknownEncoding)( +- parser->m_unknownEncodingMem, info.map, info.convert, info.data); ++ parser->m_unknownEncodingMem, info.map, ++ info.convert ? callUnknownEncodingConvert : NULL, parser); + if (enc) { +- parser->m_unknownEncodingData = info.data; +- parser->m_unknownEncodingRelease = info.release; + parser->m_encoding = enc; + return XML_ERROR_NONE; + } ++ parser->m_unknownEncodingConvert = NULL; + } +- if (info.release != NULL) +- info.release(info.data); ++ if (parser->m_unknownEncodingRelease != NULL) ++ callUnknownEncodingRelease(parser); ++ else ++ parser->m_unknownEncodingData = NULL; + } + return XML_ERROR_UNKNOWN_ENCODING; + } +-- +2.45.4 + + +From ff413da9a45ef5755bc08a075cd34704540f2555 Mon Sep 17 00:00:00 2001 +From: Darren Carreras +Date: Mon, 17 Aug 2026 21:19:12 -0400 +Subject: [PATCH 2/2] Test custom encoding callback reentry protection + +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: https://github.com/libexpat/libexpat/pull/1322.patch +--- + tests/misc_tests.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 55 insertions(+) + +diff --git a/tests/misc_tests.c b/tests/misc_tests.c +index 82b4b54..04538c2 100644 +--- a/tests/misc_tests.c ++++ b/tests/misc_tests.c +@@ -839,6 +839,60 @@ START_TEST(test_misc_resume_parser_forbidden_from_handler) { + } + END_TEST + ++typedef struct { ++ XML_Parser parser; ++ int converterCallCount; ++ int releaseCallCount; ++} EncodingCallbackData; ++ ++static int XMLCALL ++reentrant_encoding_converter(void *userData, const char *s) { ++ EncodingCallbackData *const data = userData; ++ UNUSED_P(s); ++ data->converterCallCount++; ++ forbidden_calls_character_handler(data->parser, NULL, 0); ++ return 'A'; ++} ++ ++static void XMLCALL ++reentrant_encoding_release(void *userData) { ++ EncodingCallbackData *const data = userData; ++ data->releaseCallCount++; ++ forbidden_calls_character_handler(data->parser, NULL, 0); ++} ++ ++static int XMLCALL ++reentrant_encoding_handler(void *userData, const XML_Char *name, ++ XML_Encoding *info) { ++ EncodingCallbackData *const data = userData; ++ UNUSED_P(name); ++ ++ for (int i = 0; i < 256; i++) ++ info->map[i] = i; ++ info->map[0x80] = -2; // Route byte 0x80 through the custom converter. ++ info->data = data; ++ info->convert = reentrant_encoding_converter; ++ info->release = reentrant_encoding_release; ++ return XML_STATUS_OK; ++} ++ ++START_TEST(test_misc_unknown_encoding_callbacks_protected) { ++ const char *const doc ++ = "\x80\x80"; ++ XML_Parser parser = XML_ParserCreate(NULL); ++ EncodingCallbackData data = {parser, 0, 0}; ++ XML_SetUnknownEncodingHandler(parser, reentrant_encoding_handler, &data); ++ ++ assert_true(XML_Parse(parser, doc, (int)strlen(doc), /*isFinal=*/XML_TRUE) ++ == XML_STATUS_OK); ++ assert_true(data.converterCallCount > 0); ++ assert_true(data.releaseCallCount == 0); // Released by XML_ParserFree below. ++ ++ XML_ParserFree(parser); ++ assert_true(data.releaseCallCount == 1); ++} ++END_TEST ++ + // General attack payload idea by Jason Kratzer of Mozilla + START_TEST(test_misc_low_surrogate_mozilla_bug_2053153) { + const char doc_before[] = "<\0!\0D\0O\0C\0T\0Y\0P\0E\0 \0d\0 \0[\0\n\0" +@@ -936,6 +990,7 @@ make_miscellaneous_test_case(Suite *s) { + tcase_add_test(tc_misc, test_misc_no_infinite_loop_issue_1161); + tcase_add_test(tc_misc, test_misc_calls_forbidden_from_handlers); + tcase_add_test(tc_misc, test_misc_resume_parser_forbidden_from_handler); ++ tcase_add_test(tc_misc, test_misc_unknown_encoding_callbacks_protected); + tcase_add_test(tc_misc, test_misc_input_2gb); + tcase_add_test(tc_misc, test_misc_low_surrogate_mozilla_bug_2053153); + } +-- +2.45.4 + diff --git a/SPECS/expat/expat.spec b/SPECS/expat/expat.spec index 52a3a8a7947..a171512fb7d 100644 --- a/SPECS/expat/expat.spec +++ b/SPECS/expat/expat.spec @@ -2,13 +2,17 @@ Summary: An XML parser library Name: expat Version: 2.8.3 -Release: 1%{?dist} +Release: 2%{?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-2026-66046.patch +Patch1: CVE-2026-76641.patch +Patch2: CVE-2026-76956.patch +Patch3: CVE-2026-76957.patch Requires: %{name}-libs = %{version}-%{release} %description @@ -66,6 +70,9 @@ rm -rf %{buildroot}/%{_docdir}/%{name} %{_libdir}/libexpat.so.1* %changelog +* Fri Aug 21 2026 Azure Linux Security Servicing Account - 2.8.3-2 +- Patch for CVE-2026-76957, CVE-2026-76956, CVE-2026-76641, CVE-2026-66046 + * Tue Aug 11 2026 CBL-Mariner Servicing Account - 2.8.3-1 - Auto-upgrade to 2.8.3 - for CVE-2026-72522 diff --git a/toolkit/resources/manifests/package/pkggen_core_aarch64.txt b/toolkit/resources/manifests/package/pkggen_core_aarch64.txt index bfd77ffca3b..1349a92959d 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.8.3-1.azl3.aarch64.rpm -expat-devel-2.8.3-1.azl3.aarch64.rpm -expat-libs-2.8.3-1.azl3.aarch64.rpm +expat-2.8.3-2.azl3.aarch64.rpm +expat-devel-2.8.3-2.azl3.aarch64.rpm +expat-libs-2.8.3-2.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 e7a3d8ddb73..92a56a6eace 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.8.3-1.azl3.x86_64.rpm -expat-devel-2.8.3-1.azl3.x86_64.rpm -expat-libs-2.8.3-1.azl3.x86_64.rpm +expat-2.8.3-2.azl3.x86_64.rpm +expat-devel-2.8.3-2.azl3.x86_64.rpm +expat-libs-2.8.3-2.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 916884293a6..383d57f1047 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.8.3-1.azl3.aarch64.rpm -expat-debuginfo-2.8.3-1.azl3.aarch64.rpm -expat-devel-2.8.3-1.azl3.aarch64.rpm -expat-libs-2.8.3-1.azl3.aarch64.rpm +expat-2.8.3-2.azl3.aarch64.rpm +expat-debuginfo-2.8.3-2.azl3.aarch64.rpm +expat-devel-2.8.3-2.azl3.aarch64.rpm +expat-libs-2.8.3-2.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 eeb8e5570a7..dd5c9bc946b 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.8.3-1.azl3.x86_64.rpm -expat-debuginfo-2.8.3-1.azl3.x86_64.rpm -expat-devel-2.8.3-1.azl3.x86_64.rpm -expat-libs-2.8.3-1.azl3.x86_64.rpm +expat-2.8.3-2.azl3.x86_64.rpm +expat-debuginfo-2.8.3-2.azl3.x86_64.rpm +expat-devel-2.8.3-2.azl3.x86_64.rpm +expat-libs-2.8.3-2.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