mirror of
https://github.com/openbsd/src.git
synced 2026-06-18 15:23:33 +02:00
Update libexpat to version 2.7.5.
Relevant for OpenBSD are security fixes #1158 #1161 #1162 #1163, other changes #1156 #1153. Library bump is not necessary. CVE-2026-32776 CVE-2026-32777 CVE-2026-32778 tested and OK tb@
This commit is contained in:
+53
-12
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
<body>
|
||||
<div>
|
||||
<h1>
|
||||
The Expat XML Parser <small>Release 2.7.4</small>
|
||||
The Expat XML Parser <small>Release 2.7.5</small>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
Copyright (c) 2001-2002 Greg Stein <gstein@users.sourceforge.net>
|
||||
Copyright (c) 2002-2006 Karl Waclawek <karl@waclawek.net>
|
||||
Copyright (c) 2016 Cristian Rodríguez <crrodriguez@opensuse.org>
|
||||
Copyright (c) 2016-2025 Sebastian Pipping <sebastian@pipping.org>
|
||||
Copyright (c) 2016-2026 Sebastian Pipping <sebastian@pipping.org>
|
||||
Copyright (c) 2017 Rhodri James <rhodri@wildebeest.org.uk>
|
||||
Copyright (c) 2018 Yury Gribov <tetra2005@gmail.com>
|
||||
Licensed under the MIT license:
|
||||
|
||||
+54
-11
@@ -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
|
||||
"<!"). In a DTD context, that isn't legal.
|
||||
"<!" or "<?"). In a DTD context, that isn't legal.
|
||||
*/
|
||||
else if (tok == XML_TOK_INSTANCE_START) {
|
||||
*nextPtr = next;
|
||||
@@ -5175,6 +5177,15 @@ entityValueProcessor(XML_Parser parser, const char *s, const char *end,
|
||||
/* found end of entity value - can store it now */
|
||||
return storeEntityValue(parser, enc, s, end, XML_ACCOUNT_DIRECT, NULL);
|
||||
}
|
||||
/* 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
|
||||
"<!" or "<?"). In a DTD context, that isn't legal.
|
||||
*/
|
||||
else if (tok == XML_TOK_INSTANCE_START) {
|
||||
*nextPtr = next;
|
||||
return XML_ERROR_SYNTAX;
|
||||
}
|
||||
|
||||
start = next;
|
||||
}
|
||||
}
|
||||
@@ -6789,7 +6800,14 @@ storeEntityValue(XML_Parser parser, const ENCODING *enc,
|
||||
return XML_ERROR_NO_MEMORY;
|
||||
}
|
||||
|
||||
const char *next;
|
||||
const char *next = entityTextPtr;
|
||||
|
||||
/* Nothing to tokenize. */
|
||||
if (entityTextPtr >= entityTextEnd) {
|
||||
result = XML_ERROR_NONE;
|
||||
goto endEntityValue;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
next
|
||||
= entityTextPtr; /* XmlEntityValueTok doesn't always set the last arg */
|
||||
@@ -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)) {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
Copyright (c) 2002-2006 Karl Waclawek <karl@waclawek.net>
|
||||
Copyright (c) 2002-2003 Fred L. Drake, Jr. <fdrake@users.sourceforge.net>
|
||||
Copyright (c) 2005-2009 Steven Solie <steven@solie.ca>
|
||||
Copyright (c) 2016-2023 Sebastian Pipping <sebastian@pipping.org>
|
||||
Copyright (c) 2016-2026 Sebastian Pipping <sebastian@pipping.org>
|
||||
Copyright (c) 2017 Rhodri James <rhodri@wildebeest.org.uk>
|
||||
Copyright (c) 2019 David Loffredo <loffredo@steptools.com>
|
||||
Copyright (c) 2021 Donghee Na <donghee.na@python.org>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
Copyright (c) 2002 Greg Stein <gstein@users.sourceforge.net>
|
||||
Copyright (c) 2002-2016 Karl Waclawek <karl@waclawek.net>
|
||||
Copyright (c) 2005-2009 Steven Solie <steven@solie.ca>
|
||||
Copyright (c) 2016-2024 Sebastian Pipping <sebastian@pipping.org>
|
||||
Copyright (c) 2016-2026 Sebastian Pipping <sebastian@pipping.org>
|
||||
Copyright (c) 2016 Pascal Cuoq <cuoq@trust-in-soft.com>
|
||||
Copyright (c) 2016 Don Lewis <truckman@apache.org>
|
||||
Copyright (c) 2017 Rhodri James <rhodri@wildebeest.org.uk>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
Copyright (c) 2002 Greg Stein <gstein@users.sourceforge.net>
|
||||
Copyright (c) 2002 Fred L. Drake, Jr. <fdrake@users.sourceforge.net>
|
||||
Copyright (c) 2002-2006 Karl Waclawek <karl@waclawek.net>
|
||||
Copyright (c) 2017-2021 Sebastian Pipping <sebastian@pipping.org>
|
||||
Copyright (c) 2017-2026 Sebastian Pipping <sebastian@pipping.org>
|
||||
Copyright (c) 2025 Alfonso Gregory <gfunni234@gmail.com>
|
||||
Licensed under the MIT license:
|
||||
|
||||
|
||||
@@ -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 = "<!DOCTYPE r SYSTEM \"ext.dtd\"><r/>";
|
||||
ExtOption options[] = {
|
||||
{XCS("ext.dtd"), "<!ENTITY % pe SYSTEM \"empty\">"
|
||||
"<!ENTITY ge \"%pe;\">"},
|
||||
{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);
|
||||
|
||||
@@ -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 = "<!DOCTYPE d SYSTEM 'secondary.txt'>";
|
||||
|
||||
struct ExtOption options[] = {
|
||||
{XCS("secondary.txt"),
|
||||
"<!ENTITY % p SYSTEM 'tertiary.txt'><!ENTITY g '%p;'>"},
|
||||
{XCS("tertiary.txt"), "<?xml version='1.0'?><a"},
|
||||
{NULL, NULL},
|
||||
};
|
||||
|
||||
XML_SetUserData(parser, options);
|
||||
XML_SetParamEntityParsing(parser, XML_PARAM_ENTITY_PARSING_ALWAYS);
|
||||
XML_SetExternalEntityRefHandler(parser, external_entity_optioner);
|
||||
|
||||
assert_true(_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE)
|
||||
== XML_STATUS_ERROR);
|
||||
|
||||
#if defined(XML_DTD)
|
||||
assert_true(XML_GetErrorCode(parser) == XML_ERROR_EXTERNAL_ENTITY_HANDLING);
|
||||
#else
|
||||
assert_true(XML_GetErrorCode(parser) == XML_ERROR_NO_ELEMENTS);
|
||||
#endif
|
||||
|
||||
XML_ParserFree(parser);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
void
|
||||
make_miscellaneous_test_case(Suite *s) {
|
||||
TCase *tc_misc = tcase_create("miscellaneous tests");
|
||||
@@ -802,4 +831,5 @@ make_miscellaneous_test_case(Suite *s) {
|
||||
tcase_add_test(tc_misc, test_misc_expected_event_ptr_issue_980);
|
||||
tcase_add_test(tc_misc, test_misc_sync_entity_tolerated);
|
||||
tcase_add_test(tc_misc, test_misc_async_entity_rejected);
|
||||
tcase_add_test(tc_misc, test_misc_no_infinite_loop_issue_1161);
|
||||
}
|
||||
|
||||
@@ -1505,6 +1505,32 @@ START_TEST(test_nsalloc_prefixed_element) {
|
||||
}
|
||||
END_TEST
|
||||
|
||||
/* Verify that retry after OOM in setContext() does not crash.
|
||||
*/
|
||||
START_TEST(test_nsalloc_setContext_zombie) {
|
||||
const char *text = "<doc>Hello</doc>";
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user