untrusted comment: verify with openbsd-78-base.pub RWS3/nvFmk4SWZ4xz+JrQHj9xYKXtG74q5dSx2y18vB5B4FhxTeeG8OYwsBEYd8uxnlZhWu5vzfDBcm0qBjQC8/6lgBfDcmRiQI= OpenBSD 7.8 errata 024, March 21, 2026: In libexpat fix denial of service due to NULL dereference and infinite loop. CVE-2026-32776 CVE-2026-32777 CVE-2026-32778 Apply by doing: signify -Vep /etc/signify/openbsd-78-base.pub -x 024_expat.patch.sig \ -m - | (cd /usr/src && patch -p0) And then rebuild and install libexpat: cd /usr/src/lib/libexpat make obj make make install Index: lib/libexpat/Changes =================================================================== RCS file: /cvs/src/lib/libexpat/Changes,v diff -u -p -r1.32.2.1 Changes --- lib/libexpat/Changes 6 Feb 2026 21:26:12 -0000 1.32.2.1 +++ lib/libexpat/Changes 18 Mar 2026 18:09:32 -0000 @@ -42,6 +42,27 @@ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 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. + + Other changes: + #1156 Address Cppcheck >=2.20.0 warnings + #1153 tests: Make test_buffer_can_grow_to_max work for MinGW on + Ubuntu 24.04 + + Security fixes: #1131 CVE-2026-24515 -- Function XML_ExternalEntityParserCreate failed to copy the encoding handler data passed to XML_SetUnknownEncodingHandler from the parent to the new Index: lib/libexpat/lib/xmlparse.c =================================================================== RCS file: /cvs/src/lib/libexpat/lib/xmlparse.c,v diff -u -p -r1.44.2.1 xmlparse.c --- lib/libexpat/lib/xmlparse.c 6 Feb 2026 21:26:12 -0000 1.44.2.1 +++ lib/libexpat/lib/xmlparse.c 18 Mar 2026 18:09:33 -0000 @@ -590,6 +590,8 @@ static XML_Char *poolStoreString(STRING_ 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 pars } /* 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_ else { if (! poolAppendChar(&parser->m_tempPool, XML_T('\0'))) return XML_FALSE; - prefix - = (PREFIX *)lookup(parser, &dtd->prefixes, - poolStart(&parser->m_tempPool), sizeof(PREFIX)); - if (! prefix) + const XML_Char *const prefixName = poolCopyStringNoFinish( + &dtd->pool, poolStart(&parser->m_tempPool)); + if (! prefixName) { return XML_FALSE; - if (prefix->name == poolStart(&parser->m_tempPool)) { - prefix->name = poolCopyString(&dtd->pool, prefix->name); - if (! prefix->name) - 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; + poolDiscard(&parser->m_tempPool); } for (context = s + 1; *context != CONTEXT_SEP && *context != XML_T('\0'); @@ -8034,6 +8060,23 @@ poolCopyString(STRING_POOL *pool, const s = pool->start; poolFinish(pool); 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 * Index: lib/libexpat/tests/basic_tests.c =================================================================== RCS file: /cvs/src/lib/libexpat/tests/basic_tests.c,v diff -u -p -r1.7.2.1 basic_tests.c --- lib/libexpat/tests/basic_tests.c 6 Feb 2026 21:26:12 -0000 1.7.2.1 +++ lib/libexpat/tests/basic_tests.c 18 Mar 2026 18:09:33 -0000 @@ -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_cl 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); Index: lib/libexpat/tests/misc_tests.c =================================================================== RCS file: /cvs/src/lib/libexpat/tests/misc_tests.c,v diff -u -p -r1.8 misc_tests.c --- lib/libexpat/tests/misc_tests.c 25 Sep 2025 19:05:10 -0000 1.8 +++ lib/libexpat/tests/misc_tests.c 18 Mar 2026 18:09:33 -0000 @@ -771,6 +771,35 @@ START_TEST(test_misc_async_entity_reject } 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"), "