File expat-CVE-2026-32778.patch of Package expat.43408

From 576b61e42feeea704253cb7c7bedb2eeb3754387 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 1/2] 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.

Co-authored-by: Sebastian Pipping <sebastian@pipping.org>
---
 expat/lib/xmlparse.c | 43 +++++++++++++++++++++++++++++++++++--------
 1 file changed, 35 insertions(+), 8 deletions(-)

diff --git a/expat/lib/xmlparse.c b/expat/lib/xmlparse.c
index 086fca591..5a49f3f5d 100644
--- expat/lib/xmlparse.c
+++ expat/lib/xmlparse.c
@@ -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,
@@ -7439,16 +7441,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');
@@ -8036,6 +8046,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)) {

From d5fa769b7a7290a7e2c4a0b2287106dec9b3c030 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 2/2] test that we do not end up with a zombie PREFIX in the
 pool

---
 expat/tests/nsalloc_tests.c | 27 +++++++++++++++++++++++++++
 1 file changed, 27 insertions(+)

diff --git a/expat/tests/nsalloc_tests.c b/expat/tests/nsalloc_tests.c
index 60fa87f83..9e26d4ee1 100644
--- expat/tests/nsalloc_tests.c
+++ expat/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);
 }
openSUSE Build Service is sponsored by