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"), "Hello";
+ unsigned int i;
+ const unsigned int max_alloc_count = 30;
+
+ for (i = 0; i < max_alloc_count; i++) {
+ g_allocation_count = (int)i;
+ if (XML_Parse(g_parser, text, (int)strlen(text), XML_TRUE)
+ != XML_STATUS_ERROR)
+ break;
+ /* Retry on the same parser — must not crash */
+ g_allocation_count = ALLOC_ALWAYS_SUCCEED;
+ XML_Parse(g_parser, text, (int)strlen(text), XML_TRUE);
+
+ nsalloc_teardown();
+ nsalloc_setup();
+ }
+ if (i == 0)
+ fail("Parsing worked despite failing allocations");
+ else if (i == max_alloc_count)
+ fail("Parsing failed even at maximum allocation count");
+}
+END_TEST
+
void
make_nsalloc_test_case(Suite *s) {
TCase *tc_nsalloc = tcase_create("namespace allocation tests");
@@ -1539,4 +1565,5 @@ make_nsalloc_test_case(Suite *s) {
tcase_add_test__if_xml_ge(tc_nsalloc, test_nsalloc_long_default_in_ext);
tcase_add_test(tc_nsalloc, test_nsalloc_long_systemid_in_ext);
tcase_add_test(tc_nsalloc, test_nsalloc_prefixed_element);
+ tcase_add_test(tc_nsalloc, test_nsalloc_setContext_zombie);
}