untrusted comment: verify with openbsd-77-base.pub
RWSbCCUoGpcxVS5ONne15POQh1Vi+1C2jXphXtyBQWG+0AYHcAfkoeOaJ8mks8LIlpWQJl5AskObM6sn7UkRh5uoKtCXCvQ3wwQ=

OpenBSD 7.7 errata 040, May 8, 2026:

libexpat uses more entropy to protect against hash flooding.
CVE-2026-41080

Apply by doing:
    signify -Vep /etc/signify/openbsd-77-base.pub -x 040_expat.patch.sig \
        -m - | (cd /usr/src && patch -p0)

And then rebuild and install libexpat:
    cd /usr/src/lib/libexpat
    make obj
    make
    make install

Index: lib/libexpat/Changes
===================================================================
RCS file: /cvs/src/lib/libexpat/Changes,v
diff -u -p -r1.30.4.3 Changes
--- lib/libexpat/Changes	20 Mar 2026 16:34:57 -0000	1.30.4.3
+++ lib/libexpat/Changes	30 Apr 2026 11:54:49 -0000
@@ -38,6 +38,22 @@
 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 
         Security fixes:
+       #47 #1183  CVE-2026-41080 -- The existing hash flooding protection
+                    (based on SipHash) only used 4 to 8 bytes of entropy for
+                    a salt, when 16 bytes of salt are supported by the
+                    implementation of SipHash used by Expat. Now full 16 bytes
+                    of entropy are used to improve protection against hash
+                    flooding attacks.
+                      Existing API function XML_SetHashSalt is now deprecated
+                    because of its limitations, and its use should be
+                    considered a vulnerability. Please either use the new API
+                    function XML_SetHashSalt16Bytes (with known-high-quality
+                    entropy input only!) instead, or leave the derivation of
+                    a 16-bytes hash salt from high quality entropy to Expat's
+                    internal machinery (by *not* calling either of the two
+                    XML_SetHashSalt* functions).
+
+        Security fixes:
            #1158  CVE-2026-32776 -- Fix NULL function pointer dereference for
                     empty external parameter entities; it takes use of both
                     functions XML_ExternalEntityParserCreate and
Index: lib/libexpat/lib/internal.h
===================================================================
RCS file: /cvs/src/lib/libexpat/lib/internal.h,v
diff -u -p -r1.13.4.1 internal.h
--- lib/libexpat/lib/internal.h	28 Sep 2025 22:22:54 -0000	1.13.4.1
+++ lib/libexpat/lib/internal.h	30 Apr 2026 11:54:49 -0000
@@ -113,6 +113,7 @@
 #if defined(_WIN32)                                                            \
     && (! defined(__USE_MINGW_ANSI_STDIO)                                      \
         || (1 - __USE_MINGW_ANSI_STDIO - 1 == 0))
+#  define EXPAT_FMT_LLX(midpart) "%" midpart "I64x"
 #  define EXPAT_FMT_ULL(midpart) "%" midpart "I64u"
 #  if defined(_WIN64) // Note: modifiers "td" and "zu" do not work for MinGW
 #    define EXPAT_FMT_PTRDIFF_T(midpart) "%" midpart "I64d"
@@ -122,6 +123,7 @@
 #    define EXPAT_FMT_SIZE_T(midpart) "%" midpart "u"
 #  endif
 #else
+#  define EXPAT_FMT_LLX(midpart) "%" midpart "llx"
 #  define EXPAT_FMT_ULL(midpart) "%" midpart "llu"
 #  if ! defined(ULONG_MAX)
 #    error Compiler did not define ULONG_MAX for us
Index: lib/libexpat/lib/xmlparse.c
===================================================================
RCS file: /cvs/src/lib/libexpat/lib/xmlparse.c,v
diff -u -p -r1.42.4.3 xmlparse.c
--- lib/libexpat/lib/xmlparse.c	20 Mar 2026 16:34:57 -0000	1.42.4.3
+++ lib/libexpat/lib/xmlparse.c	30 Apr 2026 11:54:50 -0000
@@ -604,7 +604,7 @@ static ELEMENT_TYPE *getElementType(XML_
 
 static XML_Char *copyString(const XML_Char *s, XML_Parser parser);
 
-static unsigned long generate_hash_secret_salt(XML_Parser parser);
+static struct sipkey generate_hash_secret_salt(void);
 static XML_Bool startParsing(XML_Parser parser);
 
 static XML_Parser parserCreate(const XML_Char *encodingName,
@@ -777,7 +777,8 @@ struct XML_ParserStruct {
   XML_Bool m_useForeignDTD;
   enum XML_ParamEntityParsing m_paramEntityParsing;
 #endif
-  unsigned long m_hash_secret_salt;
+  struct sipkey m_hash_secret_salt_128;
+  XML_Bool m_hash_secret_salt_set;
 #if XML_GE == 1
   ACCOUNTING m_accounting;
   MALLOC_TRACKER m_alloc_tracker;
@@ -1177,65 +1178,63 @@ gather_time_entropy(void) {
 
 #endif /* ! defined(HAVE_ARC4RANDOM_BUF) && ! defined(HAVE_ARC4RANDOM) */
 
-static unsigned long
-ENTROPY_DEBUG(const char *label, unsigned long entropy) {
+static struct sipkey
+ENTROPY_DEBUG(const char *label, struct sipkey entropy_128) {
   if (getDebugLevel("EXPAT_ENTROPY_DEBUG", 0) >= 1u) {
-    fprintf(stderr, "expat: Entropy: %s --> 0x%0*lx (%lu bytes)\n", label,
-            (int)sizeof(entropy) * 2, entropy, (unsigned long)sizeof(entropy));
-  }
-  return entropy;
-}
-
-static unsigned long
-generate_hash_secret_salt(XML_Parser parser) {
-  unsigned long entropy;
-  (void)parser;
+    fprintf(stderr,
+            "expat: Entropy: %s --> [0x" EXPAT_FMT_LLX(
+                "016") ", 0x" EXPAT_FMT_LLX("016") "] (16 bytes)\n",
+            label, (unsigned long long)entropy_128.k[0],
+            (unsigned long long)entropy_128.k[1]);
+  }
+  return entropy_128;
+}
+ 
+static struct sipkey
+generate_hash_secret_salt(void) {
+  struct sipkey entropy;
 
   /* "Failproof" high quality providers: */
 #if defined(HAVE_ARC4RANDOM_BUF)
   arc4random_buf(&entropy, sizeof(entropy));
   return ENTROPY_DEBUG("arc4random_buf", entropy);
 #elif defined(HAVE_ARC4RANDOM)
-  writeRandomBytes_arc4random((void *)&entropy, sizeof(entropy));
+  writeRandomBytes_arc4random(&entropy, sizeof(entropy));
   return ENTROPY_DEBUG("arc4random", entropy);
 #else
   /* Try high quality providers first .. */
 #  ifdef _WIN32
-  if (writeRandomBytes_rand_s((void *)&entropy, sizeof(entropy))) {
+  if (writeRandomBytes_rand_s(&entropy, sizeof(entropy))) {
     return ENTROPY_DEBUG("rand_s", entropy);
   }
 #  elif defined(HAVE_GETRANDOM) || defined(HAVE_SYSCALL_GETRANDOM)
-  if (writeRandomBytes_getrandom_nonblock((void *)&entropy, sizeof(entropy))) {
+  if (writeRandomBytes_getrandom_nonblock(&entropy, sizeof(entropy))) {
     return ENTROPY_DEBUG("getrandom", entropy);
   }
 #  endif
 #  if ! defined(_WIN32) && defined(XML_DEV_URANDOM)
-  if (writeRandomBytes_dev_urandom((void *)&entropy, sizeof(entropy))) {
+  if (writeRandomBytes_dev_urandom(&entropy, sizeof(entropy))) {
     return ENTROPY_DEBUG("/dev/urandom", entropy);
   }
 #  endif /* ! defined(_WIN32) && defined(XML_DEV_URANDOM) */
   /* .. and self-made low quality for backup: */
 
+  entropy.k[0] = 0;
+  entropy.k[1] = gather_time_entropy();
   /* Process ID is 0 bits entropy if attacker has local access */
-  entropy = gather_time_entropy() ^ getpid();
+  entropy.k[1] ^= getpid();
 
   /* Factors are 2^31-1 and 2^61-1 (Mersenne primes M31 and M61) */
   if (sizeof(unsigned long) == 4) {
-    return ENTROPY_DEBUG("fallback(4)", entropy * 2147483647);
+    entropy.k[1] *= 2147483647;
+    return ENTROPY_DEBUG("fallback(4)", entropy);
   } else {
-    return ENTROPY_DEBUG("fallback(8)",
-                         entropy * (unsigned long)2305843009213693951ULL);
+    entropy.k[1] *= 2305843009213693951ULL;
+    return ENTROPY_DEBUG("fallback(8)", entropy);
   }
 #endif
 }
 
-static unsigned long
-get_hash_secret_salt(XML_Parser parser) {
-  if (parser->m_parentParser != NULL)
-    return get_hash_secret_salt(parser->m_parentParser);
-  return parser->m_hash_secret_salt;
-}
-
 static enum XML_Error
 callProcessor(XML_Parser parser, const char *start, const char *end,
               const char **endPtr) {
@@ -1304,8 +1303,10 @@ callProcessor(XML_Parser parser, const c
 static XML_Bool /* only valid for root parser */
 startParsing(XML_Parser parser) {
   /* hash functions must be initialized before setContext() is called */
-  if (parser->m_hash_secret_salt == 0)
-    parser->m_hash_secret_salt = generate_hash_secret_salt(parser);
+  if (parser->m_hash_secret_salt_set != XML_TRUE) {
+    parser->m_hash_secret_salt_128 = generate_hash_secret_salt();
+    parser->m_hash_secret_salt_set = XML_TRUE;
+  }
   if (parser->m_ns) {
     /* implicit context only set for root parser, since child
        parsers (i.e. external entity parsers) will inherit it
@@ -1593,7 +1594,9 @@ parserInit(XML_Parser parser, const XML_
   parser->m_useForeignDTD = XML_FALSE;
   parser->m_paramEntityParsing = XML_PARAM_ENTITY_PARSING_NEVER;
 #endif
-  parser->m_hash_secret_salt = 0;
+  parser->m_hash_secret_salt_128.k[0] = 0;
+  parser->m_hash_secret_salt_128.k[1] = 0;
+  parser->m_hash_secret_salt_set = XML_FALSE;
 
 #if XML_GE == 1
   memset(&parser->m_accounting, 0, sizeof(ACCOUNTING));
@@ -1760,7 +1763,8 @@ XML_ExternalEntityParserCreate(XML_Parse
      from hash tables associated with either parser without us having
      to worry which hash secrets each table has.
   */
-  unsigned long oldhash_secret_salt;
+  struct sipkey oldhash_secret_salt_128;
+  XML_Bool oldhash_secret_salt_set;
   XML_Bool oldReparseDeferralEnabled;
 
   /* Validate the oldParser parameter before we pull everything out of it */
@@ -1806,7 +1810,8 @@ XML_ExternalEntityParserCreate(XML_Parse
      from hash tables associated with either parser without us having
      to worry which hash secrets each table has.
   */
-  oldhash_secret_salt = parser->m_hash_secret_salt;
+  oldhash_secret_salt_128 = parser->m_hash_secret_salt_128;
+  oldhash_secret_salt_set = parser->m_hash_secret_salt_set;
   oldReparseDeferralEnabled = parser->m_reparseDeferralEnabled;
 
 #ifdef XML_DTD
@@ -1861,7 +1866,8 @@ XML_ExternalEntityParserCreate(XML_Parse
     parser->m_externalEntityRefHandlerArg = oldExternalEntityRefHandlerArg;
   parser->m_defaultExpandInternalEntities = oldDefaultExpandInternalEntities;
   parser->m_ns_triplets = oldns_triplets;
-  parser->m_hash_secret_salt = oldhash_secret_salt;
+  parser->m_hash_secret_salt_128 = oldhash_secret_salt_128;
+  parser->m_hash_secret_salt_set = oldhash_secret_salt_set;
   parser->m_reparseDeferralEnabled = oldReparseDeferralEnabled;
   parser->m_parentParser = oldParser;
 #ifdef XML_DTD
@@ -2314,7 +2320,19 @@ XML_SetHashSalt(XML_Parser parser, unsig
   /* block after XML_Parse()/XML_ParseBuffer() has been called */
   if (parserBusy(parser))
     return 0;
-  parser->m_hash_secret_salt = hash_salt;
+
+  parser->m_hash_secret_salt_128.k[0] = 0;
+  parser->m_hash_secret_salt_128.k[1] = hash_salt;
+
+  if (hash_salt != 0) { // to remain backwards compatible
+    parser->m_hash_secret_salt_set = XML_TRUE;
+
+    if (sizeof(unsigned long) == 4)
+      ENTROPY_DEBUG("explicit(4)", parser->m_hash_secret_salt_128);
+    else
+      ENTROPY_DEBUG("explicit(8)", parser->m_hash_secret_salt_128);
+  }
+
   return 1;
 }
 
@@ -7792,8 +7810,10 @@ keylen(KEY s) {
 
 static void
 copy_salt_to_sipkey(XML_Parser parser, struct sipkey *key) {
-  key->k[0] = 0;
-  key->k[1] = get_hash_secret_salt(parser);
+  const XML_Parser rootParser = getRootParserOf(parser, NULL);
+  assert(! rootParser->m_parentParser);
+
+  *key = rootParser->m_hash_secret_salt_128;
 }
 
 static unsigned long FASTCALL
