new file mode 100644
@@ -0,0 +1,90 @@
+From fa84dfe9d7c817315e3d77ae632aeecf6fe2cd84 Mon Sep 17 00:00:00 2001
+From: laserbear <10689391+Laserbear@users.noreply.github.com>
+Date: Sun, 8 Mar 2026 17:28:06 -0700
+Subject: [PATCH] copy prefix name to pool before lookup
+
+.. so that we cannot end up with a zombie PREFIX in the pool
+that has NULL for a name.
+
+CVE: CVE-2026-32778
+Upstream-Status: Backport [https://github.com/libexpat/libexpat/commit/576b61e42feeea704253cb7c7bedb2eeb3754387]
+
+Co-authored-by: Sebastian Pipping <sebastian@pipping.org>
+(cherry picked from commit 576b61e42feeea704253cb7c7bedb2eeb3754387)
+Signed-off-by: Deepak Rathore <deeratho@cisco.com>
+---
+ lib/xmlparse.c | 43 +++++++++++++++++++++++++++++++++++--------
+ 1 file changed, 35 insertions(+), 8 deletions(-)
+
+diff --git a/lib/xmlparse.c b/lib/xmlparse.c
+index c5bd7059..eee283a4 100644
+--- a/lib/xmlparse.c
++++ b/lib/xmlparse.c
+@@ -591,6 +591,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,
+@@ -7446,16 +7448,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));
+- 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');
+@@ -8044,6 +8054,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)) {
+--
+2.51.0
new file mode 100644
@@ -0,0 +1,59 @@
+From 0b3d3b977ccaf18684ce951b818c56a7e704fb29 Mon Sep 17 00:00:00 2001
+From: laserbear <10689391+Laserbear@users.noreply.github.com>
+Date: Sun, 8 Mar 2026 17:28:06 -0700
+Subject: [PATCH] test that we do not end up with a zombie PREFIX in the pool
+
+CVE: CVE-2026-32778
+Upstream-Status: Backport [https://github.com/libexpat/libexpat/commit/d5fa769b7a7290a7e2c4a0b2287106dec9b3c030]
+
+(cherry picked from commit d5fa769b7a7290a7e2c4a0b2287106dec9b3c030)
+Signed-off-by: Deepak Rathore <deeratho@cisco.com>
+---
+ tests/nsalloc_tests.c | 27 +++++++++++++++++++++++++++
+ 1 file changed, 27 insertions(+)
+
+diff --git a/tests/nsalloc_tests.c b/tests/nsalloc_tests.c
+index 60fa87f8..9e26d4ee 100644
+--- a/tests/nsalloc_tests.c
++++ b/tests/nsalloc_tests.c
+@@ -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);
+ }
+--
+2.51.0
@@ -13,6 +13,8 @@ SRC_URI = "${GITHUB_BASE_URI}/download/R_${VERSION_TAG}/expat-${PV}.tar.bz2 \
file://CVE-2026-32776.patch \
file://CVE-2026-32777_p1.patch \
file://CVE-2026-32777_p2.patch \
+ file://CVE-2026-32778_p1.patch \
+ file://CVE-2026-32778_p2.patch \
"
GITHUB_BASE_URI = "https://github.com/libexpat/libexpat/releases/"