From b878628b560a2ba1e11b3a12ff8df0dab7d6b8bb 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.

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: Hugo SIMELIERE <simeliere.hugo@non.se.com>
---
 lib/xmlparse.c | 43 +++++++++++++++++++++++++++++++++++--------
 1 file changed, 35 insertions(+), 8 deletions(-)

diff --git a/lib/xmlparse.c b/lib/xmlparse.c
index bfb8ac58..9bc67f38 100644
--- a/lib/xmlparse.c
+++ b/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,
@@ -7443,16 +7445,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');
@@ -8041,6 +8051,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.43.0

