diff mbox series

[whinlatter,3/3] expat: Fix CVE-2026-32778

Message ID 20260322100714.667175-1-deeratho@cisco.com
State New
Headers show
Series [whinlatter,1/3] expat: Fix CVE-2026-32776 | expand

Commit Message

From: Deepak Rathore <deeratho@cisco.com>

Pick the patch [1] and [2] as mentioned in [3].

[1] https://github.com/libexpat/libexpat/commit/576b61e42feeea704253cb7c7bedb2eeb3754387
[2] https://github.com/libexpat/libexpat/commit/d5fa769b7a7290a7e2c4a0b2287106dec9b3c030
[3] https://security-tracker.debian.org/tracker/CVE-2026-32778

Signed-off-by: Deepak Rathore <deeratho@cisco.com>
diff mbox series

Patch

diff --git a/meta/recipes-core/expat/expat/CVE-2026-32778_p1.patch b/meta/recipes-core/expat/expat/CVE-2026-32778_p1.patch
new file mode 100644
index 0000000000..35a7c62865
--- /dev/null
+++ b/meta/recipes-core/expat/expat/CVE-2026-32778_p1.patch
@@ -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
diff --git a/meta/recipes-core/expat/expat/CVE-2026-32778_p2.patch b/meta/recipes-core/expat/expat/CVE-2026-32778_p2.patch
new file mode 100644
index 0000000000..0cbf2dd347
--- /dev/null
+++ b/meta/recipes-core/expat/expat/CVE-2026-32778_p2.patch
@@ -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
diff --git a/meta/recipes-core/expat/expat_2.7.4.bb b/meta/recipes-core/expat/expat_2.7.4.bb
index da6e4bb657..f1eff49688 100644
--- a/meta/recipes-core/expat/expat_2.7.4.bb
+++ b/meta/recipes-core/expat/expat_2.7.4.bb
@@ -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/"