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

From 5be25657583ea91b09025c858b4785834c20f59c Mon Sep 17 00:00:00 2001
From: Francesco Bertolaccini <francesco.bertolaccini@trailofbits.com>
Date: Tue, 3 Mar 2026 16:41:43 +0100
Subject: [PATCH] Fix NULL function-pointer dereference for empty external
 parameter entities

When an external parameter entity with empty text is referenced inside
an entity declaration value, the sub-parser created to handle it receives
0 bytes of input.  Processing enters entityValueInitProcessor which calls
storeEntityValue() with the parser's encoding; since no bytes were ever
processed, encoding detection has not yet occurred and the encoding is
still the initial probing encoding set up by XmlInitEncoding().  That
encoding only populates scanners[] (for prolog and content), not
literalScanners[].  XmlEntityValueTok() calls through
literalScanners[XML_ENTITY_VALUE_LITERAL] which is NULL, causing a
SEGV.

Skip the tokenization loop entirely when entityTextPtr >= entityTextEnd,
and initialize the `next` pointer before the early exit so that callers
(callStoreEntityValue) receive a valid value through nextPtr.
---
 expat/lib/xmlparse.c      |  9 ++++++++-
 expat/tests/basic_tests.c | 19 +++++++++++++++++++
 2 files changed, 27 insertions(+), 1 deletion(-)

diff --git a/expat/lib/xmlparse.c b/expat/lib/xmlparse.c
index 086fca591..a5a34c1af 100644
--- expat/lib/xmlparse.c
+++ expat/lib/xmlparse.c
@@ -6789,7 +6789,14 @@ storeEntityValue(XML_Parser parser, const ENCODING *enc,
       return XML_ERROR_NO_MEMORY;
   }
 
-  const char *next;
+  const char *next = entityTextPtr;
+
+  /* Nothing to tokenize. */
+  if (entityTextPtr >= entityTextEnd) {
+    result = XML_ERROR_NONE;
+    goto endEntityValue;
+  }
+
   for (;;) {
     next
         = entityTextPtr; /* XmlEntityValueTok doesn't always set the last arg */
diff --git a/expat/tests/basic_tests.c b/expat/tests/basic_tests.c
index 60b6421df..02d1d5fd3 100644
--- expat/tests/basic_tests.c
+++ expat/tests/basic_tests.c
@@ -6258,6 +6258,24 @@ START_TEST(test_varying_buffer_fills) {
 }
 END_TEST
 
+START_TEST(test_empty_ext_param_entity_in_value) {
+  const char *text = "<!DOCTYPE r SYSTEM \"ext.dtd\"><r/>";
+  ExtOption options[] = {
+      {XCS("ext.dtd"), "<!ENTITY % pe SYSTEM \"empty\">"
+                       "<!ENTITY ge \"%pe;\">"},
+      {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");
@@ -6505,6 +6523,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);
openSUSE Build Service is sponsored by