git: 659a27e48969 - stable/15 - contrib/expat: import expat 2.8.1

From: Philip Paeps <philip_at_FreeBSD.org>
Date: Wed, 20 May 2026 03:00:25 UTC
The branch stable/15 has been updated by philip:

URL: https://cgit.FreeBSD.org/src/commit/?id=659a27e48969f49a1b737791a14c2cc6a1626d00

commit 659a27e48969f49a1b737791a14c2cc6a1626d00
Author:     Philip Paeps <philip@FreeBSD.org>
AuthorDate: 2026-05-13 03:17:00 +0000
Commit:     Philip Paeps <philip@FreeBSD.org>
CommitDate: 2026-05-20 02:50:51 +0000

    contrib/expat: import expat 2.8.1
    
    Changes: https://github.com/libexpat/libexpat/blob/R_2_8_1/expat/Changes
    
    Security:       CVE-2026-45186
    
    (cherry picked from commit 9cc9b8b372842b9a941d235c5e9949a214e5284f)
---
 contrib/expat/Changes             |  26 ++++
 contrib/expat/README.md           |   2 +-
 contrib/expat/configure.ac        |   2 +-
 contrib/expat/doc/reference.html  |   2 +-
 contrib/expat/doc/xmlwf.1         |   2 +-
 contrib/expat/doc/xmlwf.xml       |   2 +-
 contrib/expat/lib/expat.h         |   2 +-
 contrib/expat/lib/xmlparse.c      |  36 ++++-
 contrib/expat/tests/alloc_tests.c |   4 +-
 contrib/expat/tests/basic_tests.c | 310 ++++++++++++++++++++++++++++++++++++--
 contrib/expat/tests/common.c      |   5 +-
 contrib/expat/tests/handlers.c    |  34 +++--
 contrib/expat/tests/handlers.h    |   2 +
 contrib/expat/tests/memcheck.c    |   6 +-
 contrib/expat/tests/minicheck.c   |  10 +-
 contrib/expat/tests/misc_tests.c  |   7 +-
 contrib/expat/tests/structdata.c  |   7 +-
 contrib/expat/xmlwf/unixfilemap.c |   5 +-
 contrib/expat/xmlwf/xmlfile.c     |   4 +-
 contrib/expat/xmlwf/xmlwf.c       |  83 +++++-----
 lib/libexpat/expat_config.h       |   6 +-
 lib/libexpat/libbsdxml.3          |   4 +-
 22 files changed, 450 insertions(+), 111 deletions(-)

diff --git a/contrib/expat/Changes b/contrib/expat/Changes
index 87611eea5c01..313b3dc60cd0 100644
--- a/contrib/expat/Changes
+++ b/contrib/expat/Changes
@@ -29,6 +29,32 @@
 !! THANK YOU!                        Sebastian Pipping -- Berlin, 2026-03-17 !!
 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 
+Release 2.8.1 Sun May 10 2026
+        Security fixes:
+           #1216  CVE-2026-45186 -- Fix quadratic runtime from attribute name
+                    collision checks that allowed denial of service attacks
+                    through moderately sized crafted XML input (CWE-407).
+                    Please note that a layer of compression around XML can
+                    significantly reduce the minimum attack payload size.
+
+        Other changes:
+     #1209 #1213  Drop more casts related to `void *` that C99 does not need
+           #1213  xmlwf: Streamline use of `mmap`
+     #1214 #1217  Version info bumped from 13:0:12 (libexpat*.so.1.12.0)
+                    to 13:1:12 (libexpat*.so.1.12.1); see https://verbump.de/
+                    for what these numbers do
+
+        Infrastructure:
+           #1210  CI: Cover compilation with Visual Studio 18 2026 on Windows
+           #1215  CI: Cover compilation for ARM64 on Windows
+           #1212  CI: Bump WASI SDK from 32 to 33
+
+        Special thanks to:
+            Berkay Eren Ürün
+            Matthew Fernandez
+            Nick Wellnhofer
+            Tania Somanna
+
 Release 2.8.0 Fri April 24 2026
         Security fixes:
        #47 #1183  CVE-2026-41080 -- The existing hash flooding protection
diff --git a/contrib/expat/README.md b/contrib/expat/README.md
index 619e60b2d16b..7a5ed463b6d5 100644
--- a/contrib/expat/README.md
+++ b/contrib/expat/README.md
@@ -11,7 +11,7 @@
 > at the top of the `Changes` file.
 
 
-# Expat, Release 2.8.0
+# Expat, Release 2.8.1
 
 This is Expat, a C99 library for parsing
 [XML 1.0 Fourth Edition](https://www.w3.org/TR/2006/REC-xml-20060816/), started by
diff --git a/contrib/expat/configure.ac b/contrib/expat/configure.ac
index 87633650f321..fb492dfd79f7 100644
--- a/contrib/expat/configure.ac
+++ b/contrib/expat/configure.ac
@@ -90,7 +90,7 @@ dnl If the API changes incompatibly set LIBAGE back to 0
 dnl
 
 LIBCURRENT=13  # sync
-LIBREVISION=0  # with
+LIBREVISION=1  # with
 LIBAGE=12      # CMakeLists.txt!
 
 AC_CONFIG_HEADERS([expat_config.h])
diff --git a/contrib/expat/doc/reference.html b/contrib/expat/doc/reference.html
index 195bd183fd82..07d331ed7b56 100644
--- a/contrib/expat/doc/reference.html
+++ b/contrib/expat/doc/reference.html
@@ -53,7 +53,7 @@
   <body>
     <div>
       <h1>
-        The Expat XML Parser <small>Release 2.8.0</small>
+        The Expat XML Parser <small>Release 2.8.1</small>
       </h1>
     </div>
 
diff --git a/contrib/expat/doc/xmlwf.1 b/contrib/expat/doc/xmlwf.1
index 0736f110e3c1..12760944a5da 100644
--- a/contrib/expat/doc/xmlwf.1
+++ b/contrib/expat/doc/xmlwf.1
@@ -5,7 +5,7 @@
 \\$2 \(la\\$1\(ra\\$3
 ..
 .if \n(.g .mso www.tmac
-.TH XMLWF 1 "April 24, 2026" "" ""
+.TH XMLWF 1 "May 10, 2026" "" ""
 .SH NAME
 xmlwf \- Determines if an XML document is well-formed
 .SH SYNOPSIS
diff --git a/contrib/expat/doc/xmlwf.xml b/contrib/expat/doc/xmlwf.xml
index 3a3897750cb1..529dbba16722 100644
--- a/contrib/expat/doc/xmlwf.xml
+++ b/contrib/expat/doc/xmlwf.xml
@@ -21,7 +21,7 @@
           "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd" [
   <!ENTITY dhfirstname "<firstname>Scott</firstname>">
   <!ENTITY dhsurname   "<surname>Bronson</surname>">
-  <!ENTITY dhdate      "<date>April 24, 2026</date>">
+  <!ENTITY dhdate      "<date>May 10, 2026</date>">
   <!-- Please adjust this^^ date whenever cutting a new release. -->
   <!ENTITY dhsection   "<manvolnum>1</manvolnum>">
   <!ENTITY dhemail     "<email>bronson@rinspin.com</email>">
diff --git a/contrib/expat/lib/expat.h b/contrib/expat/lib/expat.h
index 79c609f19aa4..ec3f58544cb0 100644
--- a/contrib/expat/lib/expat.h
+++ b/contrib/expat/lib/expat.h
@@ -1094,7 +1094,7 @@ XML_SetReparseDeferralEnabled(XML_Parser parser, XML_Bool enabled);
 */
 #  define XML_MAJOR_VERSION 2
 #  define XML_MINOR_VERSION 8
-#  define XML_MICRO_VERSION 0
+#  define XML_MICRO_VERSION 1
 
 #  ifdef __cplusplus
 }
diff --git a/contrib/expat/lib/xmlparse.c b/contrib/expat/lib/xmlparse.c
index 620d1e4b46e2..890a9389f15d 100644
--- a/contrib/expat/lib/xmlparse.c
+++ b/contrib/expat/lib/xmlparse.c
@@ -1,4 +1,4 @@
-/* a5d18f6a50f536615ac1c70304f87d94f99cc85a86b502188952440610ccf0f8 (2.8.0+)
+/* 75ef4224f81c052e9e5aeea2ac7de75357d2169ff9908e39edc08b9dc3052513 (2.8.1+)
                             __  __            _
                          ___\ \/ /_ __   __ _| |_
                         / _ \\  /| '_ \ / _` | __|
@@ -387,6 +387,7 @@ typedef struct {
   int nDefaultAtts;
   int allocDefaultAtts;
   DEFAULT_ATTRIBUTE *defaultAtts;
+  HASH_TABLE defaultAttsNames;
 } ELEMENT_TYPE;
 
 typedef struct {
@@ -3769,6 +3770,8 @@ storeAtts(XML_Parser parser, const ENCODING *enc, const char *attStr,
                                          sizeof(ELEMENT_TYPE));
     if (! elementType)
       return XML_ERROR_NO_MEMORY;
+    if (! elementType->defaultAttsNames.parser)
+      hashTableInit(&(elementType->defaultAttsNames), parser);
     if (parser->m_ns && ! setElementTypePrefix(parser, elementType))
       return XML_ERROR_NO_MEMORY;
   }
@@ -7102,10 +7105,10 @@ defineAttribute(ELEMENT_TYPE *type, ATTRIBUTE_ID *attId, XML_Bool isCdata,
   if (value || isId) {
     /* The handling of default attributes gets messed up if we have
        a default which duplicates a non-default. */
-    int i;
-    for (i = 0; i < type->nDefaultAtts; i++)
-      if (attId == type->defaultAtts[i].id)
-        return 1;
+    NAMED *const nameFound
+        = (NAMED *)lookup(parser, &(type->defaultAttsNames), attId->name, 0);
+    if (nameFound)
+      return 1;
     if (isId && ! type->idAtt && ! attId->xmlns)
       type->idAtt = attId;
   }
@@ -7152,6 +7155,12 @@ defineAttribute(ELEMENT_TYPE *type, ATTRIBUTE_ID *attId, XML_Bool isCdata,
   att->isCdata = isCdata;
   if (! isCdata)
     attId->maybeTokenized = XML_TRUE;
+
+  NAMED *const nameAddedOrFound = (NAMED *)lookup(
+      parser, &(type->defaultAttsNames), attId->name, sizeof(NAMED));
+  if (! nameAddedOrFound)
+    return 0;
+
   type->nDefaultAtts += 1;
   return 1;
 }
@@ -7477,6 +7486,7 @@ dtdReset(DTD *p, XML_Parser parser) {
     ELEMENT_TYPE *e = (ELEMENT_TYPE *)hashTableIterNext(&iter);
     if (! e)
       break;
+    hashTableDestroy(&(e->defaultAttsNames));
     if (e->allocDefaultAtts != 0)
       FREE(parser, e->defaultAtts);
   }
@@ -7518,6 +7528,7 @@ dtdDestroy(DTD *p, XML_Bool isDocEntity, XML_Parser parser) {
     ELEMENT_TYPE *e = (ELEMENT_TYPE *)hashTableIterNext(&iter);
     if (! e)
       break;
+    hashTableDestroy(&(e->defaultAttsNames));
     if (e->allocDefaultAtts != 0)
       FREE(parser, e->defaultAtts);
   }
@@ -7611,6 +7622,10 @@ dtdCopy(XML_Parser oldParser, DTD *newDtd, const DTD *oldDtd,
                                   sizeof(ELEMENT_TYPE));
     if (! newE)
       return 0;
+
+    if (! newE->defaultAttsNames.parser)
+      hashTableInit(&(newE->defaultAttsNames), parser);
+
     if (oldE->nDefaultAtts) {
       /* Detect and prevent integer overflow.
        * The preprocessor guard addresses the "always false" warning
@@ -7635,8 +7650,9 @@ dtdCopy(XML_Parser oldParser, DTD *newDtd, const DTD *oldDtd,
       newE->prefix = (PREFIX *)lookup(oldParser, &(newDtd->prefixes),
                                       oldE->prefix->name, 0);
     for (i = 0; i < newE->nDefaultAtts; i++) {
+      const XML_Char *const attributeName = oldE->defaultAtts[i].id->name;
       newE->defaultAtts[i].id = (ATTRIBUTE_ID *)lookup(
-          oldParser, &(newDtd->attributeIds), oldE->defaultAtts[i].id->name, 0);
+          oldParser, &(newDtd->attributeIds), attributeName, 0);
       newE->defaultAtts[i].isCdata = oldE->defaultAtts[i].isCdata;
       if (oldE->defaultAtts[i].value) {
         newE->defaultAtts[i].value
@@ -7645,6 +7661,12 @@ dtdCopy(XML_Parser oldParser, DTD *newDtd, const DTD *oldDtd,
           return 0;
       } else
         newE->defaultAtts[i].value = NULL;
+
+      NAMED *const nameAddedOrFound = (NAMED *)lookup(
+          parser, &(newE->defaultAttsNames), attributeName, sizeof(NAMED));
+      if (! nameAddedOrFound) {
+        return 0;
+      }
     }
   }
 
@@ -8391,6 +8413,8 @@ getElementType(XML_Parser parser, const ENCODING *enc, const char *ptr,
                                sizeof(ELEMENT_TYPE));
   if (! ret)
     return NULL;
+  if (! ret->defaultAttsNames.parser)
+    hashTableInit(&(ret->defaultAttsNames), getRootParserOf(parser, NULL));
   if (ret->name != name)
     poolDiscard(&dtd->pool);
   else {
diff --git a/contrib/expat/tests/alloc_tests.c b/contrib/expat/tests/alloc_tests.c
index 5ae6c6a72025..433bf611808a 100644
--- a/contrib/expat/tests/alloc_tests.c
+++ b/contrib/expat/tests/alloc_tests.c
@@ -20,6 +20,7 @@
    Copyright (c) 2021      Donghee Na <donghee.na@python.org>
    Copyright (c) 2023      Sony Corporation / Snild Dolkow <snild@sony.com>
    Copyright (c) 2025      Berkay Eren Ürün <berkay.ueruen@siemens.com>
+   Copyright (c) 2026      Matthew Fernandez <matthew.fernandez@gmail.com>
    Licensed under the MIT license:
 
    Permission is  hereby granted,  free of charge,  to any  person obtaining
@@ -2136,8 +2137,7 @@ START_TEST(test_alloc_tracker_pointer_alignment) {
   XML_Parser parser = XML_ParserCreate(NULL);
 #if XML_GE == 1
   assert_true(sizeof(long long) >= sizeof(size_t)); // self-test
-  long long *const ptr
-      = (long long *)expat_malloc(parser, 4 * sizeof(long long), -1);
+  long long *const ptr = expat_malloc(parser, 4 * sizeof(long long), -1);
   ptr[0] = 0LL;
   ptr[1] = 1LL;
   ptr[2] = 2LL;
diff --git a/contrib/expat/tests/basic_tests.c b/contrib/expat/tests/basic_tests.c
index 537c7e0e5a5c..b5088bcda988 100644
--- a/contrib/expat/tests/basic_tests.c
+++ b/contrib/expat/tests/basic_tests.c
@@ -19,8 +19,9 @@
    Copyright (c) 2020      Tim Gates <tim.gates@iress.com>
    Copyright (c) 2021      Donghee Na <donghee.na@python.org>
    Copyright (c) 2023-2024 Sony Corporation / Snild Dolkow <snild@sony.com>
-   Copyright (c) 2024-2025 Berkay Eren Ürün <berkay.ueruen@siemens.com>
+   Copyright (c) 2024-2026 Berkay Eren Ürün <berkay.ueruen@siemens.com>
    Copyright (c) 2026      Francesco Bertolaccini
+   Copyright (c) 2026      Matthew Fernandez <matthew.fernandez@gmail.com>
    Licensed under the MIT license:
 
    Permission is  hereby granted,  free of charge,  to any  person obtaining
@@ -2491,11 +2492,9 @@ START_TEST(test_attributes) {
                          {XCS("id"), XCS("one")},
                          {NULL, NULL}};
   AttrInfo tag_info[] = {{XCS("c"), XCS("3")}, {NULL, NULL}};
-  ElementInfo info[] = {{XCS("doc"), 3, XCS("id"), NULL},
-                        {XCS("tag"), 1, NULL, NULL},
-                        {NULL, 0, NULL, NULL}};
-  info[0].attributes = doc_info;
-  info[1].attributes = tag_info;
+  ElementInfo info[] = {{XCS("doc"), 3, 0, XCS("id"), doc_info},
+                        {XCS("tag"), 1, 0, NULL, tag_info},
+                        {NULL, 0, 0, NULL, NULL}};
 
   XML_Parser parser = XML_ParserCreate(NULL);
   assert_true(parser != NULL);
@@ -2514,6 +2513,279 @@ START_TEST(test_attributes) {
 }
 END_TEST
 
+START_TEST(test_duplicate_cdata_attribute) {
+  /*
+  https://www.w3.org/TR/xml/#attdecls
+
+  Test the following statement from the linked specification:
+    When more than one definition is provided for the same attribute of a given
+    element type, the first declaration is binding and later declarations are
+    ignored.
+  */
+
+  const char *text
+      = "<!DOCTYPE doc [\n"
+        "  <!ATTLIST doc attribute CDATA 'expected' attribute CDATA 'ignored'>\n"
+        "]>\n"
+        "<doc/>\n";
+  AttrInfo doc_info[] = {{XCS("attribute"), XCS("expected")}, {NULL, NULL}};
+  ElementInfo info[]
+      = {{XCS("doc"), 0, 1, NULL, doc_info}, {NULL, 0, 0, NULL, NULL}};
+
+  XML_Parser parser = XML_ParserCreate(NULL);
+  assert_true(parser != NULL);
+
+  ParserAndElementInfo parserAndElementInfos = {
+      parser,
+      info,
+  };
+
+  XML_SetStartElementHandler(parser, counting_start_element_handler);
+  XML_SetUserData(parser, &parserAndElementInfos);
+
+  if (_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE)
+      != XML_STATUS_OK)
+    xml_failure(parser);
+
+  XML_ParserFree(parser);
+}
+END_TEST
+
+START_TEST(test_duplicate_id_attribute_1) {
+  /*
+  https://www.w3.org/TR/xml/#attdecls
+
+  Test the following statement from the linked specification:
+    When more than one definition is provided for the same attribute of a given
+    element type, the first declaration is binding and later declarations are
+    ignored.
+  */
+
+  const char *text
+      = "<!DOCTYPE doc [\n"
+        "  <!ATTLIST doc identifier CDATA 'expected' identifier ID #REQUIRED>\n"
+        "]>\n"
+        "<doc/>\n";
+  AttrInfo doc_info[] = {{XCS("identifier"), XCS("expected")}, {NULL, NULL}};
+  ElementInfo info[]
+      = {{XCS("doc"), 0, 1, NULL, doc_info}, {NULL, 0, 0, NULL, NULL}};
+
+  XML_Parser parser = XML_ParserCreate(NULL);
+  assert_true(parser != NULL);
+
+  ParserAndElementInfo parserAndElementInfos = {
+      parser,
+      info,
+  };
+
+  XML_SetStartElementHandler(parser, counting_start_element_handler);
+  XML_SetUserData(parser, &parserAndElementInfos);
+
+  if (_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE)
+      != XML_STATUS_OK)
+    xml_failure(parser);
+
+  XML_ParserFree(parser);
+}
+END_TEST
+
+START_TEST(test_duplicate_id_attribute_2) {
+  /*
+  https://www.w3.org/TR/xml/#attdecls
+
+  Test the following statement from the linked specification:
+    When more than one definition is provided for the same attribute of a given
+    element type, the first declaration is binding and later declarations are
+    ignored.
+  */
+
+  const char *text
+      = "<!DOCTYPE doc [\n"
+        "  <!ATTLIST doc identifier ID #REQUIRED identifier CDATA 'unexpected'>\n"
+        "]>\n"
+        "<doc/>\n";
+  AttrInfo doc_info[] = {{NULL, NULL}};
+
+  ElementInfo info[]
+      = {{XCS("doc"), 0, 0, NULL, doc_info}, {NULL, 0, 0, NULL, NULL}};
+
+  XML_Parser parser = XML_ParserCreate(NULL);
+  assert_true(parser != NULL);
+
+  ParserAndElementInfo parserAndElementInfos = {
+      parser,
+      info,
+  };
+
+  XML_SetStartElementHandler(parser, counting_start_element_handler);
+  XML_SetUserData(parser, &parserAndElementInfos);
+
+  if (_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE)
+      != XML_STATUS_OK)
+    xml_failure(parser);
+
+  XML_ParserFree(parser);
+}
+END_TEST
+
+START_TEST(test_duplicate_cdata_attribute_multiple_attlistdecl) {
+  /*
+  https://www.w3.org/TR/xml/#attdecls
+
+  Test the following statement from the linked specification:
+    When more than one AttlistDecl is provided for a given element type,
+    the contents of all those provided are merged.
+  */
+  const char *text = "<!DOCTYPE doc [\n"
+                     "  <!ATTLIST doc attribute CDATA 'expected'>\n"
+                     "  <!ATTLIST doc attribute CDATA 'ignored'>\n"
+                     "]>\n"
+                     "<doc/>\n";
+  AttrInfo doc_info[] = {{XCS("attribute"), XCS("expected")}, {NULL, NULL}};
+  ElementInfo info[]
+      = {{XCS("doc"), 0, 1, NULL, doc_info}, {NULL, 0, 0, NULL, NULL}};
+
+  XML_Parser parser = XML_ParserCreate(NULL);
+  assert_true(parser != NULL);
+
+  ParserAndElementInfo parserAndElementInfos = {
+      parser,
+      info,
+  };
+
+  XML_SetStartElementHandler(parser, counting_start_element_handler);
+  XML_SetUserData(parser, &parserAndElementInfos);
+
+  if (_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE)
+      != XML_STATUS_OK)
+    xml_failure(parser);
+
+  XML_ParserFree(parser);
+}
+END_TEST
+
+START_TEST(test_duplicate_cdata_attribute_multiple_attlistdecl_2) {
+  /*
+  https://www.w3.org/TR/xml/#attdecls
+
+  Test the following statement from the linked specification:
+    When more than one AttlistDecl is provided for a given element type,
+    the contents of all those provided are merged.
+  */
+  const char *text = "<!DOCTYPE doc [\n"
+                     "  <!ATTLIST doc attribute CDATA 'expected_doc'>\n"
+                     "  <!ATTLIST tag attribute CDATA 'expected_tag'>\n"
+                     "  <!ATTLIST doc attribute CDATA 'ignored_doc'>\n"
+                     "]>\n"
+                     "<doc><tag></tag></doc>\n";
+  AttrInfo doc_info[] = {{XCS("attribute"), XCS("expected_doc")}, {NULL, NULL}};
+  AttrInfo tag_info[] = {{XCS("attribute"), XCS("expected_tag")}, {NULL, NULL}};
+  ElementInfo info[] = {{XCS("doc"), 0, 1, NULL, doc_info},
+                        {XCS("tag"), 0, 1, NULL, tag_info},
+                        {NULL, 0, 0, NULL, NULL}};
+
+  XML_Parser parser = XML_ParserCreate(NULL);
+  assert_true(parser != NULL);
+
+  ParserAndElementInfo parserAndElementInfos = {
+      parser,
+      info,
+  };
+
+  XML_SetStartElementHandler(parser, counting_start_element_handler);
+  XML_SetUserData(parser, &parserAndElementInfos);
+
+  if (_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE)
+      != XML_STATUS_OK)
+    xml_failure(parser);
+
+  XML_ParserFree(parser);
+}
+END_TEST
+
+START_TEST(test_duplicate_cdata_attribute_multiple_attlistdecl_3) {
+  /*
+  https://www.w3.org/TR/xml/#attdecls
+
+  Test the following statement from the linked specification:
+    When more than one AttlistDecl is provided for a given element type,
+    the contents of all those provided are merged.
+  */
+  const char *text
+      = "<!DOCTYPE doc [\n"
+        "  <!ATTLIST doc attribute CDATA 'expected_doc'>\n"
+        "  <!ATTLIST tag attribute CDATA 'expected_tag'>\n"
+        "  <!ATTLIST doc second_attribute CDATA 'second_expected_doc' attribute CDATA 'ignored_doc'>\n"
+        "]>\n"
+        "<doc><tag></tag></doc>\n";
+  AttrInfo doc_info[] = {{XCS("attribute"), XCS("expected_doc")},
+                         {XCS("second_attribute"), XCS("second_expected_doc")},
+                         {NULL, NULL}};
+  AttrInfo tag_info[] = {{XCS("attribute"), XCS("expected_tag")}, {NULL, NULL}};
+  ElementInfo info[] = {{XCS("doc"), 0, 2, NULL, doc_info},
+                        {XCS("tag"), 0, 1, NULL, tag_info},
+                        {NULL, 0, 0, NULL, NULL}};
+
+  XML_Parser parser = XML_ParserCreate(NULL);
+  assert_true(parser != NULL);
+
+  ParserAndElementInfo parserAndElementInfos = {
+      parser,
+      info,
+  };
+
+  XML_SetStartElementHandler(parser, counting_start_element_handler);
+  XML_SetUserData(parser, &parserAndElementInfos);
+
+  if (_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE)
+      != XML_STATUS_OK)
+    xml_failure(parser);
+
+  XML_ParserFree(parser);
+}
+END_TEST
+
+START_TEST(test_duplicate_id_attribute_multiple_attlistdecl) {
+  /*
+  https://www.w3.org/TR/xml/#attdecls
+
+  Test the following statement from the linked specification:
+    When more than one AttlistDecl is provided for a given element type,
+    the contents of all those provided are merged.
+  */
+  const char *text = "<!DOCTYPE doc [\n"
+                     "  <!ATTLIST doc identifier ID #REQUIRED>\n"
+                     "  <!ATTLIST tag identifier CDATA 'identifier_tag'>\n"
+                     "  <!ATTLIST doc identifier CDATA 'ignored'>\n"
+                     "]>\n"
+                     "<doc identifier='doc_identity'><tag></tag></doc>\n";
+  AttrInfo doc_info[]
+      = {{XCS("identifier"), XCS("doc_identity")}, {NULL, NULL}};
+  AttrInfo tag_info[]
+      = {{XCS("identifier"), XCS("identifier_tag")}, {NULL, NULL}};
+  ElementInfo info[] = {{XCS("doc"), 1, 0, XCS("identifier"), doc_info},
+                        {XCS("tag"), 0, 1, NULL, tag_info},
+                        {NULL, 0, 0, NULL, NULL}};
+
+  XML_Parser parser = XML_ParserCreate(NULL);
+  assert_true(parser != NULL);
+
+  ParserAndElementInfo parserAndElementInfos = {
+      parser,
+      info,
+  };
+
+  XML_SetStartElementHandler(parser, counting_start_element_handler);
+  XML_SetUserData(parser, &parserAndElementInfos);
+
+  if (_XML_Parse_SINGLE_BYTES(parser, text, (int)strlen(text), XML_TRUE)
+      != XML_STATUS_OK)
+    xml_failure(parser);
+
+  XML_ParserFree(parser);
+}
+END_TEST
+
 /* Test reset works correctly in the middle of processing an internal
  * entity.  Exercises some obscure code in XML_ParserReset().
  */
@@ -3435,8 +3707,7 @@ external_bom_checker(XML_Parser parser, const XML_Char *context,
     fail("Could not create external entity parser");
 
   if (! xcstrcmp(systemId, XCS("004-2.ent"))) {
-    struct bom_testdata *const testdata
-        = (struct bom_testdata *)XML_GetUserData(parser);
+    struct bom_testdata *const testdata = XML_GetUserData(parser);
     const char *const external = testdata->external;
     const int split = testdata->split;
     testdata->nested_callback_happened = XML_TRUE;
@@ -5501,7 +5772,7 @@ START_TEST(test_deep_nested_entity) {
   const size_t N_LINES = 60000;
   const size_t SIZE_PER_LINE = 50;
 
-  char *const text = (char *)malloc((N_LINES + 4) * SIZE_PER_LINE);
+  char *const text = malloc((N_LINES + 4) * SIZE_PER_LINE);
   if (text == NULL) {
     fail("malloc failed");
   }
@@ -5547,7 +5818,7 @@ START_TEST(test_deep_nested_attribute_entity) {
   const size_t N_LINES = 60000;
   const size_t SIZE_PER_LINE = 100;
 
-  char *const text = (char *)malloc((N_LINES + 4) * SIZE_PER_LINE);
+  char *const text = malloc((N_LINES + 4) * SIZE_PER_LINE);
   if (text == NULL) {
     fail("malloc failed");
   }
@@ -5568,8 +5839,8 @@ START_TEST(test_deep_nested_attribute_entity) {
            (long unsigned)(N_LINES - 1));
 
   AttrInfo doc_info[] = {{XCS("name"), XCS("deepText")}, {NULL, NULL}};
-  ElementInfo info[] = {{XCS("foo"), 1, NULL, NULL}, {NULL, 0, NULL, NULL}};
-  info[0].attributes = doc_info;
+  ElementInfo info[]
+      = {{XCS("foo"), 1, 0, NULL, doc_info}, {NULL, 0, 0, NULL, NULL}};
 
   XML_Parser parser = XML_ParserCreate(NULL);
   ParserAndElementInfo parserPlusElemenInfo = {parser, info};
@@ -5590,7 +5861,7 @@ START_TEST(test_deep_nested_entity_delayed_interpretation) {
   const size_t N_LINES = 70000;
   const size_t SIZE_PER_LINE = 100;
 
-  char *const text = (char *)malloc((N_LINES + 4) * SIZE_PER_LINE);
+  char *const text = malloc((N_LINES + 4) * SIZE_PER_LINE);
   if (text == NULL) {
     fail("malloc failed");
   }
@@ -6069,7 +6340,7 @@ START_TEST(test_bypass_heuristic_when_close_to_bufsize) {
   }
 
   const int document_length = 65536;
-  char *const document = (char *)malloc(document_length);
+  char *const document = malloc(document_length);
   assert_true(document != NULL);
 
   const XML_Memory_Handling_Suite memfuncs = {
@@ -6180,7 +6451,7 @@ START_TEST(test_varying_buffer_fills) {
     return; // this test is slow, and doesn't use _XML_Parse_SINGLE_BYTES().
   }
 
-  char *const document = (char *)malloc(document_length);
+  char *const document = malloc(document_length);
   assert_true(document != NULL);
   memset(document, 'x', document_length);
   document[0] = '<';
@@ -6400,6 +6671,15 @@ make_basic_test_case(Suite *s) {
   tcase_add_test__ifdef_xml_dtd(tc_basic, test_empty_foreign_dtd);
   tcase_add_test(tc_basic, test_set_base);
   tcase_add_test(tc_basic, test_attributes);
+  tcase_add_test(tc_basic, test_duplicate_cdata_attribute);
+  tcase_add_test(tc_basic, test_duplicate_id_attribute_1);
+  tcase_add_test(tc_basic, test_duplicate_id_attribute_2);
+  tcase_add_test(tc_basic, test_duplicate_cdata_attribute_multiple_attlistdecl);
+  tcase_add_test(tc_basic,
+                 test_duplicate_cdata_attribute_multiple_attlistdecl_2);
+  tcase_add_test(tc_basic,
+                 test_duplicate_cdata_attribute_multiple_attlistdecl_3);
+  tcase_add_test(tc_basic, test_duplicate_id_attribute_multiple_attlistdecl);
   tcase_add_test__if_xml_ge(tc_basic, test_reset_in_entity);
   tcase_add_test(tc_basic, test_resume_invalid_parse);
   tcase_add_test(tc_basic, test_resume_resuspended);
diff --git a/contrib/expat/tests/common.c b/contrib/expat/tests/common.c
index b2537d0deee1..389d61f00b3a 100644
--- a/contrib/expat/tests/common.c
+++ b/contrib/expat/tests/common.c
@@ -19,6 +19,7 @@
    Copyright (c) 2020      Tim Gates <tim.gates@iress.com>
    Copyright (c) 2021      Donghee Na <donghee.na@python.org>
    Copyright (c) 2023-2024 Sony Corporation / Snild Dolkow <snild@sony.com>
+   Copyright (c) 2026      Matthew Fernandez <matthew.fernandez@gmail.com>
    Licensed under the MIT license:
 
    Permission is  hereby granted,  free of charge,  to any  person obtaining
@@ -261,7 +262,7 @@ _run_attribute_check(const char *text, const XML_Char *expected,
 void
 _run_ext_character_check(const char *text, ExtTest *test_data,
                          const XML_Char *expected, const char *file, int line) {
-  CharData *const storage = (CharData *)malloc(sizeof(CharData));
+  CharData *const storage = malloc(sizeof(CharData));
 
   CharData_Init(storage);
   test_data->storage = storage;
@@ -320,7 +321,7 @@ portable_strndup(const char *s, size_t n) {
 
   n = portable_strnlen(s, n);
 
-  char *const buffer = (char *)malloc(n + 1);
+  char *const buffer = malloc(n + 1);
   if (buffer == NULL) {
     errno = ENOMEM;
     return NULL;
diff --git a/contrib/expat/tests/handlers.c b/contrib/expat/tests/handlers.c
index e456df21c77c..cef04ed849b0 100644
--- a/contrib/expat/tests/handlers.c
+++ b/contrib/expat/tests/handlers.c
@@ -19,6 +19,8 @@
    Copyright (c) 2020      Tim Gates <tim.gates@iress.com>
    Copyright (c) 2021      Donghee Na <donghee.na@python.org>
    Copyright (c) 2023-2024 Sony Corporation / Snild Dolkow <snild@sony.com>
+   Copyright (c) 2026      Matthew Fernandez <matthew.fernandez@gmail.com>
+   Copyright (c) 2026      Berkay Eren Ürün <berkay.ueruen@siemens.com>
    Licensed under the MIT license:
 
    Permission is  hereby granted,  free of charge,  to any  person obtaining
@@ -137,7 +139,7 @@ counting_start_element_handler(void *userData, const XML_Char *name,
     fail("ID does not have the correct name");
     return;
   }
-  for (i = 0; i < info->attr_count; i++) {
+  for (i = 0; i < info->attr_count + info->default_attr_count; i++) {
     attr = info->attributes;
     while (attr->name != NULL) {
       if (! xcstrcmp(atts[0], attr->name))
@@ -155,6 +157,9 @@ counting_start_element_handler(void *userData, const XML_Char *name,
     /* Remember, two entries in atts per attribute (see above) */
     atts += 2;
   }
+
+  // Self-test that the test case's list of expected attributes is complete
+  assert_true(atts[0] == NULL);
 }
 
 void XMLCALL
@@ -423,7 +428,7 @@ int XMLCALL
 external_entity_optioner(XML_Parser parser, const XML_Char *context,
                          const XML_Char *base, const XML_Char *systemId,
                          const XML_Char *publicId) {
-  ExtOption *options = (ExtOption *)XML_GetUserData(parser);
+  ExtOption *options = XML_GetUserData(parser);
   XML_Parser ext_parser;
 
   UNUSED_P(base);
@@ -449,7 +454,7 @@ int XMLCALL
 external_entity_loader(XML_Parser parser, const XML_Char *context,
                        const XML_Char *base, const XML_Char *systemId,
                        const XML_Char *publicId) {
-  ExtTest *test_data = (ExtTest *)XML_GetUserData(parser);
+  ExtTest *test_data = XML_GetUserData(parser);
   XML_Parser extparser;
 
   UNUSED_P(base);
@@ -477,7 +482,7 @@ external_entity_faulter(XML_Parser parser, const XML_Char *context,
                         const XML_Char *base, const XML_Char *systemId,
                         const XML_Char *publicId) {
   XML_Parser ext_parser;
-  ExtFaults *fault = (ExtFaults *)XML_GetUserData(parser);
+  ExtFaults *fault = XML_GetUserData(parser);
 
   UNUSED_P(base);
   UNUSED_P(systemId);
@@ -650,7 +655,7 @@ external_entity_suspending_faulter(XML_Parser parser, const XML_Char *context,
                                    const XML_Char *systemId,
                                    const XML_Char *publicId) {
   XML_Parser ext_parser;
-  ExtFaults *fault = (ExtFaults *)XML_GetUserData(parser);
+  ExtFaults *fault = XML_GetUserData(parser);
   void *buffer;
   int parse_len = (int)strlen(fault->parse_text);
 
@@ -981,7 +986,7 @@ external_entity_valuer(XML_Parser parser, const XML_Char *context,
         == XML_STATUS_ERROR)
       xml_failure(ext_parser);
   } else if (! xcstrcmp(systemId, XCS("004-2.ent"))) {
-    ExtFaults *fault = (ExtFaults *)XML_GetUserData(parser);
+    ExtFaults *fault = XML_GetUserData(parser);
     enum XML_Status status;
     enum XML_Error error;
 
@@ -1083,7 +1088,7 @@ int XMLCALL
 external_entity_public(XML_Parser parser, const XML_Char *context,
                        const XML_Char *base, const XML_Char *systemId,
                        const XML_Char *publicId) {
-  const char *text1 = (const char *)XML_GetUserData(parser);
+  const char *text1 = XML_GetUserData(parser);
   const char *text2 = "<!ATTLIST doc a CDATA 'value'>";
   const char *text = NULL;
   XML_Parser ext_parser;
@@ -1139,7 +1144,7 @@ int XMLCALL
 external_entity_oneshot_loader(XML_Parser parser, const XML_Char *context,
                                const XML_Char *base, const XML_Char *systemId,
                                const XML_Char *publicId) {
-  ExtHdlrData *test_data = (ExtHdlrData *)XML_GetUserData(parser);
+  ExtHdlrData *test_data = XML_GetUserData(parser);
   XML_Parser ext_parser;
 
   UNUSED_P(base);
@@ -1164,7 +1169,7 @@ int XMLCALL
 external_entity_loader2(XML_Parser parser, const XML_Char *context,
                         const XML_Char *base, const XML_Char *systemId,
                         const XML_Char *publicId) {
-  ExtTest2 *test_data = (ExtTest2 *)XML_GetUserData(parser);
+  ExtTest2 *test_data = XML_GetUserData(parser);
   XML_Parser extparser;
 
   UNUSED_P(base);
@@ -1191,7 +1196,7 @@ int XMLCALL
 external_entity_faulter2(XML_Parser parser, const XML_Char *context,
                          const XML_Char *base, const XML_Char *systemId,
                          const XML_Char *publicId) {
-  ExtFaults2 *test_data = (ExtFaults2 *)XML_GetUserData(parser);
+  ExtFaults2 *test_data = XML_GetUserData(parser);
   XML_Parser extparser;
 
   UNUSED_P(base);
@@ -1309,7 +1314,7 @@ int XMLCALL
 external_entity_dbl_handler(XML_Parser parser, const XML_Char *context,
                             const XML_Char *base, const XML_Char *systemId,
                             const XML_Char *publicId) {
-  int *pcallno = (int *)XML_GetUserData(parser);
+  int *pcallno = XML_GetUserData(parser);
   int callno = *pcallno;
   const char *text;
   XML_Parser new_parser = NULL;
@@ -1366,7 +1371,7 @@ int XMLCALL
 external_entity_dbl_handler_2(XML_Parser parser, const XML_Char *context,
                               const XML_Char *base, const XML_Char *systemId,
                               const XML_Char *publicId) {
-  int *pcallno = (int *)XML_GetUserData(parser);
+  int *pcallno = XML_GetUserData(parser);
   int callno = *pcallno;
   const char *text;
   XML_Parser new_parser;
@@ -1461,7 +1466,7 @@ int XMLCALL
 external_entity_alloc(XML_Parser parser, const XML_Char *context,
                       const XML_Char *base, const XML_Char *systemId,
                       const XML_Char *publicId) {
-  const char *text = (const char *)XML_GetUserData(parser);
+  const char *text = XML_GetUserData(parser);
   XML_Parser ext_parser;
   int parse_res;
 
@@ -1516,8 +1521,7 @@ accounting_external_entity_ref_handler(XML_Parser parser,
   UNUSED_P(base);
   UNUSED_P(publicId);
 
-  const struct AccountingTestCase *const testCase
-      = (const struct AccountingTestCase *)XML_GetUserData(parser);
+  const struct AccountingTestCase *const testCase = XML_GetUserData(parser);
 
   const char *externalText = NULL;
   if (xcstrcmp(systemId, XCS("first.ent")) == 0) {
diff --git a/contrib/expat/tests/handlers.h b/contrib/expat/tests/handlers.h
index fcde27ae4940..be8f8931982c 100644
--- a/contrib/expat/tests/handlers.h
+++ b/contrib/expat/tests/handlers.h
@@ -19,6 +19,7 @@
    Copyright (c) 2020      Tim Gates <tim.gates@iress.com>
    Copyright (c) 2021      Donghee Na <donghee.na@python.org>
    Copyright (c) 2023      Sony Corporation / Snild Dolkow <snild@sony.com>
+   Copyright (c) 2026      Berkay Eren Ürün <berkay.ueruen@siemens.com>
    Licensed under the MIT license:
 
    Permission is  hereby granted,  free of charge,  to any  person obtaining
@@ -88,6 +89,7 @@ typedef struct attrInfo {
 typedef struct elementInfo {
   const XML_Char *name;
   int attr_count;
+  int default_attr_count;
   const XML_Char *id_name;
   AttrInfo *attributes;
 } ElementInfo;
diff --git a/contrib/expat/tests/memcheck.c b/contrib/expat/tests/memcheck.c
index de9254997214..2f43347e1717 100644
--- a/contrib/expat/tests/memcheck.c
+++ b/contrib/expat/tests/memcheck.c
@@ -9,6 +9,7 @@
    Copyright (c) 2017      Rhodri James <rhodri@wildebeest.org.uk>
    Copyright (c) 2017-2023 Sebastian Pipping <sebastian@pipping.org>
    Copyright (c) 2022      Sean McBride <sean@rogue-research.com>
+   Copyright (c) 2026      Matthew Fernandez <matthew.fernandez@gmail.com>
    Licensed under the MIT license:
 
    Permission is  hereby granted,  free of charge,  to any  person obtaining
@@ -55,8 +56,7 @@ static AllocationEntry *find_allocation(const void *ptr);
 /* Allocate some memory and keep track of it. */
 void *
 tracking_malloc(size_t size) {
-  AllocationEntry *const entry
-      = (AllocationEntry *)malloc(sizeof(AllocationEntry));
+  AllocationEntry *const entry = malloc(sizeof(AllocationEntry));
 
   if (entry == NULL) {
     printf("Allocator failure\n");
@@ -142,7 +142,7 @@ tracking_realloc(void *ptr, size_t size) {
   entry = find_allocation(ptr);
   if (entry == NULL) {
     printf("Attempting to realloc unallocated memory at %p\n", ptr);
-    entry = (AllocationEntry *)malloc(sizeof(AllocationEntry));
+    entry = malloc(sizeof(AllocationEntry));
     if (entry == NULL) {
       printf("Reallocator failure\n");
       return NULL;
diff --git a/contrib/expat/tests/minicheck.c b/contrib/expat/tests/minicheck.c
index baccd76b4415..d452864e00fc 100644
--- a/contrib/expat/tests/minicheck.c
+++ b/contrib/expat/tests/minicheck.c
@@ -16,6 +16,7 @@
    Copyright (c) 2018      Marco Maggi <marco.maggi-ipsu@poste.it>
    Copyright (c) 2019      David Loffredo <loffredo@steptools.com>
    Copyright (c) 2023-2024 Sony Corporation / Snild Dolkow <snild@sony.com>
+   Copyright (c) 2026      Matthew Fernandez <matthew.fernandez@gmail.com>
    Licensed under the MIT license:
 
    Permission is  hereby granted,  free of charge,  to any  person obtaining
@@ -54,7 +55,7 @@
 
 Suite *
 suite_create(const char *name) {
-  Suite *suite = (Suite *)calloc(1, sizeof(Suite));
+  Suite *suite = calloc(1, sizeof(Suite));
   if (suite != NULL) {
     suite->name = name;
   }
@@ -63,7 +64,7 @@ suite_create(const char *name) {
 
 TCase *
 tcase_create(const char *name) {
-  TCase *tc = (TCase *)calloc(1, sizeof(TCase));
+  TCase *tc = calloc(1, sizeof(TCase));
   if (tc != NULL) {
     tc->name = name;
   }
@@ -94,8 +95,7 @@ tcase_add_test(TCase *tc, tcase_test_function test) {
   if (tc->allocated == tc->ntests) {
     int nalloc = tc->allocated + 100;
     size_t new_size = sizeof(tcase_test_function) * nalloc;
-    tcase_test_function *const new_tests
-        = (tcase_test_function *)realloc(tc->tests, new_size);
+    tcase_test_function *const new_tests = realloc(tc->tests, new_size);
     assert(new_tests != NULL);
     tc->tests = new_tests;
     tc->allocated = nalloc;
@@ -130,7 +130,7 @@ suite_free(Suite *suite) {
 
 SRunner *
 srunner_create(Suite *suite) {
-  SRunner *const runner = (SRunner *)calloc(1, sizeof(SRunner));
+  SRunner *const runner = calloc(1, sizeof(SRunner));
   if (runner != NULL) {
     runner->suite = suite;
   }
diff --git a/contrib/expat/tests/misc_tests.c b/contrib/expat/tests/misc_tests.c
index 6d1a2f1488b0..015fb87295e6 100644
--- a/contrib/expat/tests/misc_tests.c
+++ b/contrib/expat/tests/misc_tests.c
@@ -20,6 +20,7 @@
    Copyright (c) 2021      Donghee Na <donghee.na@python.org>
    Copyright (c) 2023      Sony Corporation / Snild Dolkow <snild@sony.com>
    Copyright (c) 2025      Berkay Eren Ürün <berkay.ueruen@siemens.com>
+   Copyright (c) 2026      Matthew Fernandez <matthew.fernandez@gmail.com>
    Licensed under the MIT license:
 
    Permission is  hereby granted,  free of charge,  to any  person obtaining
@@ -212,7 +213,7 @@ START_TEST(test_misc_version) {
   if (! versions_equal(&read_version, &parsed_version))
     fail("Version mismatch");
 
-  if (xcstrcmp(version_text, XCS("expat_2.8.0"))
+  if (xcstrcmp(version_text, XCS("expat_2.8.1"))
       != 0) /* needs bump on releases */
     fail("XML_*_VERSION in expat.h out of sync?\n");
 }
*** 421 LINES SKIPPED ***