diff --git a/lib/libexpat/Changes b/lib/libexpat/Changes index cafb1921eb0..2b3704a69b7 100644 --- a/lib/libexpat/Changes +++ b/lib/libexpat/Changes @@ -10,31 +10,72 @@ !! ~~~~~~~~~~~~ !! !! The following topics need *additional skilled C developers* to progress !! !! in a timely manner or at all (loosely ordered by descending priority): !! +!! _______________________ !! +!! - teaming up on fixing the UNFIXED SECURITY ISSUES listed at: !! +!! """"""""""""""""""""""" !! +!! https://github.com/libexpat/libexpat/issues/1160 !! !! !! !! - teaming up on researching and fixing future security reports and !! !! ClusterFuzz findings with few-days-max response times in communication !! !! in order to (1) have a sound fix ready before the end of a 90 days !! !! grace period and (2) in a sustainable manner, !! -!! - helping Perl's XML::Parser Expat bindings with supporting Expat's !! -!! security API (https://github.com/cpan-authors/XML-Parser/issues/102): !! -!! - XML_SetAllocTrackerActivationThreshold !! -!! - XML_SetAllocTrackerMaximumAmplification !! -!! - XML_SetBillionLaughsAttackProtectionActivationThreshold !! -!! - XML_SetBillionLaughsAttackProtectionMaximumAmplification !! -!! - XML_SetReparseDeferralEnabled !! +!! !! !! - implementing and auto-testing XML 1.0r5 support !! !! (needs discussion before pull requests), !! -!! - smart ideas on fixing the Autotools CMake files generation issue !! -!! without breaking CI (needs discussion before pull requests), !! -!! - pushing migration from `int` to `size_t` further !! -!! including edge-cases test coverage (needs discussion before anything). !! !! !! !! For details, please reach out via e-mail to sebastian@pipping.org so we !! !! can schedule a voice call on the topic, in English or German. !! !! !! -!! THANK YOU! Sebastian Pipping -- Berlin, 2024-03-09 !! +!! THANK YOU! Sebastian Pipping -- Berlin, 2026-03-17 !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +Release 2.7.5 Tue March 17 2026 + Security fixes: + #1158 CVE-2026-32776 -- Fix NULL function pointer dereference for + empty external parameter entities; it takes use of both + functions XML_ExternalEntityParserCreate and + XML_SetParamEntityParsing for an application to be + vulnerable. + #1161 #1162 CVE-2026-32777 -- Protect from XML_TOK_INSTANCE_START + infinite loop in function entityValueProcessor; it takes + use of both functions XML_ExternalEntityParserCreate and + XML_SetParamEntityParsing for an application to be + vulnerable. + #1163 CVE-2026-32778 -- Fix NULL dereference in function setContext + on retry after an earlier ouf-of-memory condition; it takes + use of function XML_ParserCreateNS or XML_ParserCreate_MM + for an application to be vulnerable. + #1160 Three more unfixed vulnerabilities left + + Other changes: + #1146 #1147 Autotools: Fix condition for symbol versioning check, in + particular when compiling with slibtool (not libtool) + #1156 Address Cppcheck >=2.20.0 warnings + #1153 tests: Make test_buffer_can_grow_to_max work for MinGW on + Ubuntu 24.04 + #1157 #1159 Version info bumped from 12:2:11 (libexpat*.so.1.11.2) + to 12:3:11 (libexpat*.so.1.11.3); see https://verbump.de/ + for what these numbers do + + Infrastructure: + #1148 CI: Fix FreeBSD and Solaris CI + #1149 CI: Bump to WASI SDK 30 + #1153 CI: Adapt to breaking changes with Ubuntu 22.04 + #1156 CI: Adapt to breaking changes in Cppcheck + + Special thanks to: + Berkay Eren Ürün + Christian Ng + Fabio Scaccabarozzi + Francesco Bertolaccini + Mark Brand + Rhodri James + and + AddressSanitizer + Buttercup + OSS-Fuzz / ClusterFuzz + Trail of Bits + Release 2.7.4 Sat January 31 2026 Security fixes: #1131 CVE-2026-24515 -- Function XML_ExternalEntityParserCreate diff --git a/lib/libexpat/README.md b/lib/libexpat/README.md index 55f269428ba..a67548be7fc 100644 --- a/lib/libexpat/README.md +++ b/lib/libexpat/README.md @@ -11,7 +11,7 @@ > at the top of the `Changes` file. -# Expat, Release 2.7.4 +# Expat, Release 2.7.5 This is Expat, a C99 library for parsing [XML 1.0 Fourth Edition](https://www.w3.org/TR/2006/REC-xml-20060816/), started by diff --git a/lib/libexpat/doc/reference.html b/lib/libexpat/doc/reference.html index 0223bd7a2e8..5faa8d6515a 100644 --- a/lib/libexpat/doc/reference.html +++ b/lib/libexpat/doc/reference.html @@ -53,7 +53,7 @@

- The Expat XML Parser Release 2.7.4 + The Expat XML Parser Release 2.7.5

diff --git a/lib/libexpat/lib/expat.h b/lib/libexpat/lib/expat.h index 6c7c4186927..18dbaebde29 100644 --- a/lib/libexpat/lib/expat.h +++ b/lib/libexpat/lib/expat.h @@ -1082,7 +1082,7 @@ XML_SetReparseDeferralEnabled(XML_Parser parser, XML_Bool enabled); */ # define XML_MAJOR_VERSION 2 # define XML_MINOR_VERSION 7 -# define XML_MICRO_VERSION 4 +# define XML_MICRO_VERSION 5 # ifdef __cplusplus } diff --git a/lib/libexpat/lib/expat_external.h b/lib/libexpat/lib/expat_external.h index 080a59b7859..d9ddeb612f6 100644 --- a/lib/libexpat/lib/expat_external.h +++ b/lib/libexpat/lib/expat_external.h @@ -12,7 +12,7 @@ Copyright (c) 2001-2002 Greg Stein Copyright (c) 2002-2006 Karl Waclawek Copyright (c) 2016 Cristian Rodríguez - Copyright (c) 2016-2025 Sebastian Pipping + Copyright (c) 2016-2026 Sebastian Pipping Copyright (c) 2017 Rhodri James Copyright (c) 2018 Yury Gribov Licensed under the MIT license: diff --git a/lib/libexpat/lib/xmlparse.c b/lib/libexpat/lib/xmlparse.c index 086fca59112..0248b6651ff 100644 --- a/lib/libexpat/lib/xmlparse.c +++ b/lib/libexpat/lib/xmlparse.c @@ -1,4 +1,4 @@ -/* fab937ab8b186d7d296013669c332e6dfce2f99567882cff1f8eb24223c524a7 (2.7.4+) +/* 93c1caa66e2b0310459482516af05505b57c5cb7b96df777105308fc585c85d1 (2.7.5+) __ __ _ ___\ \/ /_ __ __ _| |_ / _ \\ /| '_ \ / _` | __| @@ -590,6 +590,8 @@ static XML_Char *poolStoreString(STRING_POOL *pool, const ENCODING *enc, static XML_Bool FASTCALL poolGrow(STRING_POOL *pool); static const XML_Char *FASTCALL poolCopyString(STRING_POOL *pool, const XML_Char *s); +static const XML_Char *FASTCALL poolCopyStringNoFinish(STRING_POOL *pool, + const XML_Char *s); static const XML_Char *poolCopyStringN(STRING_POOL *pool, const XML_Char *s, int n); static const XML_Char *FASTCALL poolAppendString(STRING_POOL *pool, @@ -5086,7 +5088,7 @@ entityValueInitProcessor(XML_Parser parser, const char *s, const char *end, } /* If we get this token, we have the start of what might be a normal tag, but not a declaration (i.e. it doesn't begin with - "= entityTextEnd) { + result = XML_ERROR_NONE; + goto endEntityValue; + } + for (;;) { next = entityTextPtr; /* XmlEntityValueTok doesn't always set the last arg */ @@ -7439,16 +7457,24 @@ setContext(XML_Parser parser, const XML_Char *context) { else { if (! poolAppendChar(&parser->m_tempPool, XML_T('\0'))) return XML_FALSE; - prefix - = (PREFIX *)lookup(parser, &dtd->prefixes, - poolStart(&parser->m_tempPool), sizeof(PREFIX)); + const XML_Char *const prefixName = poolCopyStringNoFinish( + &dtd->pool, poolStart(&parser->m_tempPool)); + if (! prefixName) { + return XML_FALSE; + } + + prefix = (PREFIX *)lookup(parser, &dtd->prefixes, prefixName, + sizeof(PREFIX)); + + const bool prefixNameUsed = prefix && prefix->name == prefixName; + if (prefixNameUsed) + poolFinish(&dtd->pool); + else + poolDiscard(&dtd->pool); + if (! prefix) return XML_FALSE; - if (prefix->name == poolStart(&parser->m_tempPool)) { - prefix->name = poolCopyString(&dtd->pool, prefix->name); - if (! prefix->name) - return XML_FALSE; - } + poolDiscard(&parser->m_tempPool); } for (context = s + 1; *context != CONTEXT_SEP && *context != XML_T('\0'); @@ -8036,6 +8062,23 @@ poolCopyString(STRING_POOL *pool, const XML_Char *s) { return s; } +// A version of `poolCopyString` that does not call `poolFinish` +// and reverts any partial advancement upon failure. +static const XML_Char *FASTCALL +poolCopyStringNoFinish(STRING_POOL *pool, const XML_Char *s) { + const XML_Char *const original = s; + do { + if (! poolAppendChar(pool, *s)) { + // Revert any previously successful advancement + const ptrdiff_t advancedBy = s - original; + if (advancedBy > 0) + pool->ptr -= advancedBy; + return NULL; + } + } while (*s++); + return pool->start; +} + static const XML_Char * poolCopyStringN(STRING_POOL *pool, const XML_Char *s, int n) { if (! pool->ptr && ! poolGrow(pool)) { diff --git a/lib/libexpat/lib/xmlrole.c b/lib/libexpat/lib/xmlrole.c index d56bee82dd2..b1dfb456e5d 100644 --- a/lib/libexpat/lib/xmlrole.c +++ b/lib/libexpat/lib/xmlrole.c @@ -12,7 +12,7 @@ Copyright (c) 2002-2006 Karl Waclawek Copyright (c) 2002-2003 Fred L. Drake, Jr. Copyright (c) 2005-2009 Steven Solie - Copyright (c) 2016-2023 Sebastian Pipping + Copyright (c) 2016-2026 Sebastian Pipping Copyright (c) 2017 Rhodri James Copyright (c) 2019 David Loffredo Copyright (c) 2021 Donghee Na diff --git a/lib/libexpat/lib/xmltok.c b/lib/libexpat/lib/xmltok.c index 32cd5f147e9..f6e5f742c92 100644 --- a/lib/libexpat/lib/xmltok.c +++ b/lib/libexpat/lib/xmltok.c @@ -12,7 +12,7 @@ Copyright (c) 2002 Greg Stein Copyright (c) 2002-2016 Karl Waclawek Copyright (c) 2005-2009 Steven Solie - Copyright (c) 2016-2024 Sebastian Pipping + Copyright (c) 2016-2026 Sebastian Pipping Copyright (c) 2016 Pascal Cuoq Copyright (c) 2016 Don Lewis Copyright (c) 2017 Rhodri James diff --git a/lib/libexpat/lib/xmltok_ns.c b/lib/libexpat/lib/xmltok_ns.c index 810ca2c6d04..1cd60de1e4f 100644 --- a/lib/libexpat/lib/xmltok_ns.c +++ b/lib/libexpat/lib/xmltok_ns.c @@ -11,7 +11,7 @@ Copyright (c) 2002 Greg Stein Copyright (c) 2002 Fred L. Drake, Jr. Copyright (c) 2002-2006 Karl Waclawek - Copyright (c) 2017-2021 Sebastian Pipping + Copyright (c) 2017-2026 Sebastian Pipping Copyright (c) 2025 Alfonso Gregory Licensed under the MIT license: diff --git a/lib/libexpat/tests/basic_tests.c b/lib/libexpat/tests/basic_tests.c index 36d6a454943..168be9c5dac 100644 --- a/lib/libexpat/tests/basic_tests.c +++ b/lib/libexpat/tests/basic_tests.c @@ -3112,12 +3112,16 @@ START_TEST(test_buffer_can_grow_to_max) { #if defined(__MINGW32__) && ! defined(__MINGW64__) // workaround for mingw/wine32 on GitHub CI not being able to reach 1GiB // Can we make a big allocation? - void *big = malloc(maxbuf); - if (! big) { + for (int i = 1; i <= 2; i++) { + void *const big = malloc(maxbuf); + if (big != NULL) { + free(big); + break; + } // The big allocation failed. Let's be a little lenient. maxbuf = maxbuf / 2; + fprintf(stderr, "Reducing maxbuf to %d...\n", maxbuf); } - free(big); #endif for (int i = 0; i < num_prefixes; ++i) { @@ -6043,6 +6047,7 @@ START_TEST(test_bypass_heuristic_when_close_to_bufsize) { const int document_length = 65536; char *const document = (char *)malloc(document_length); + assert_true(document != NULL); const XML_Memory_Handling_Suite memfuncs = { counting_malloc, @@ -6257,6 +6262,24 @@ START_TEST(test_varying_buffer_fills) { END_TEST #endif +START_TEST(test_empty_ext_param_entity_in_value) { + const char *text = ""; + ExtOption options[] = { + {XCS("ext.dtd"), "" + ""}, + {XCS("empty"), ""}, + {NULL, NULL}, + }; + + XML_SetParamEntityParsing(g_parser, XML_PARAM_ENTITY_PARSING_ALWAYS); + XML_SetExternalEntityRefHandler(g_parser, external_entity_optioner); + XML_SetUserData(g_parser, options); + if (_XML_Parse_SINGLE_BYTES(g_parser, text, (int)strlen(text), XML_TRUE) + == XML_STATUS_ERROR) + xml_failure(g_parser); +} +END_TEST + void make_basic_test_case(Suite *s) { TCase *tc_basic = tcase_create("basic tests"); @@ -6504,6 +6527,7 @@ make_basic_test_case(Suite *s) { tcase_add_test(tc_basic, test_empty_element_abort); tcase_add_test__ifdef_xml_dtd(tc_basic, test_pool_integrity_with_unfinished_attr); + tcase_add_test__ifdef_xml_dtd(tc_basic, test_empty_ext_param_entity_in_value); tcase_add_test__if_xml_ge(tc_basic, test_entity_ref_no_elements); tcase_add_test__if_xml_ge(tc_basic, test_deep_nested_entity); tcase_add_test__if_xml_ge(tc_basic, test_deep_nested_attribute_entity); diff --git a/lib/libexpat/tests/misc_tests.c b/lib/libexpat/tests/misc_tests.c index 0e178269912..1c508bd1046 100644 --- a/lib/libexpat/tests/misc_tests.c +++ b/lib/libexpat/tests/misc_tests.c @@ -212,7 +212,7 @@ START_TEST(test_misc_version) { if (! versions_equal(&read_version, &parsed_version)) fail("Version mismatch"); - if (xcstrcmp(version_text, XCS("expat_2.7.4")) + if (xcstrcmp(version_text, XCS("expat_2.7.5")) != 0) /* needs bump on releases */ fail("XML_*_VERSION in expat.h out of sync?\n"); } @@ -772,6 +772,35 @@ START_TEST(test_misc_async_entity_rejected) { } END_TEST +START_TEST(test_misc_no_infinite_loop_issue_1161) { + XML_Parser parser = XML_ParserCreate(NULL); + + const char *text = ""; + + struct ExtOption options[] = { + {XCS("secondary.txt"), + ""}, + {XCS("tertiary.txt"), "