From patchwork Tue May 5 20:52:28 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: "Marko, Peter" X-Patchwork-Id: 87555 X-Patchwork-Delegate: yoann.congal@smile.fr Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 30159CD3427 for ; Tue, 5 May 2026 20:52:53 +0000 (UTC) Received: from mta-64-226.siemens.flowmailer.net (mta-64-226.siemens.flowmailer.net [185.136.64.226]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.3864.1778014364240344840 for ; Tue, 05 May 2026 13:52:44 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=peter.marko@siemens.com header.s=fm2 header.b=H1zdJqIZ; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.226, mailfrom: fm-256628-2026050520524282fa0ebbae000207cd-npbe2_@rts-flowmailer.siemens.com) Received: by mta-64-226.siemens.flowmailer.net with ESMTPSA id 2026050520524282fa0ebbae000207cd for ; Tue, 05 May 2026 22:52:42 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm2; d=siemens.com; i=peter.marko@siemens.com; h=Date:From:Subject:To:Message-ID:MIME-Version:Content-Type:Content-Transfer-Encoding:Cc; bh=RtvNooxiXaQUfvqTejVb1c71crrd0oBPH5dEwspGkug=; b=H1zdJqIZn/oe7cqi5y7E0jEL2W06raDaGuRLzN9zAyOSwc2/PViudRMqxpnzC5enbLJNBC ndDGQTit98YkC+L6v/zOEZm+9LbBpf3we2pp5IUWLzoAA5KC4teA5zKRsA5Yl9Lswaz50NJq f4OREVO13EgBOjxLHvGWn3VHCRDqdFPSxsW020S0kfmKkotS8oUZqFL7ladJxErvfa5zqJRi wuzIwuo/W8Y5Za0Ok/TUWF7JP8iGHAQnEgLD6naZ0NBGVWI38rPqwc29/PAtk7bqLFcSz4uP SL1u623H2yIo8mobmzx4iXO6gT1Bw5cPoaiqwKkrxWlUC3o4RdR+lt2w==; From: Peter Marko To: openembedded-core@lists.openembedded.org Cc: Peter Marko Subject: [scarthgap][PATCH] expat: patch CVE-2026-41080 Date: Tue, 5 May 2026 22:52:28 +0200 Message-ID: <20260505205229.2139449-1-peter.marko@siemens.com> MIME-Version: 1.0 X-Flowmailer-Platform: Siemens Feedback-ID: 519:519-256628:519-21489:flowmailer List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Tue, 05 May 2026 20:52:53 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/236528 From: Peter Marko Pick github PR [1] mentioned in [2]. * 969af8f4654ce50d837bb9199a73d1d02d2c7e16..4ba09dc471b39a78d77e5179d0243186c0c4ff7a * dropped code which doesn't exist in 2.6.4 yet (github actions, map file) * resolved minor conflicts (formatting) * picked 2 additional commits to apply the code cleanly [1] https://github.com/libexpat/libexpat/pull/1183 [2] https://security-tracker.debian.org/tracker/CVE-2026-41080 Signed-off-by: Peter Marko --- .../expat/expat/CVE-2026-41080-01.patch | 50 ++ .../expat/expat/CVE-2026-41080-02.patch | 29 ++ .../expat/expat/CVE-2026-41080-03.patch | 467 ++++++++++++++++++ meta/recipes-core/expat/expat_2.6.4.bb | 3 + 4 files changed, 549 insertions(+) create mode 100644 meta/recipes-core/expat/expat/CVE-2026-41080-01.patch create mode 100644 meta/recipes-core/expat/expat/CVE-2026-41080-02.patch create mode 100644 meta/recipes-core/expat/expat/CVE-2026-41080-03.patch diff --git a/meta/recipes-core/expat/expat/CVE-2026-41080-01.patch b/meta/recipes-core/expat/expat/CVE-2026-41080-01.patch new file mode 100644 index 00000000000..0c6af75a5de --- /dev/null +++ b/meta/recipes-core/expat/expat/CVE-2026-41080-01.patch @@ -0,0 +1,50 @@ +From fe04a7f0ff8afe57ba33d919f368b1ba23bcda92 Mon Sep 17 00:00:00 2001 +From: Sebastian Pipping +Date: Sun, 30 Mar 2025 19:26:55 +0200 +Subject: [PATCH 1/3] lib/xmlparse.c: Address clang-tidy warning + misc-no-recursion + +CVE: CVE-2026-41080 +Upstream-Status: Backport [https://github.com/libexpat/libexpat/commit/fe04a7f0ff8afe57ba33d919f368b1ba23bcda92] +Signed-off-by: Peter Marko +--- + lib/xmlparse.c | 17 ++++++++++------- + 1 file changed, 10 insertions(+), 7 deletions(-) + +diff --git a/lib/xmlparse.c b/lib/xmlparse.c +index 9bc67f38..cb25c37b 100644 +--- a/lib/xmlparse.c ++++ b/lib/xmlparse.c +@@ -1243,9 +1243,10 @@ generate_hash_secret_salt(XML_Parser parser) { + + 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; ++ const XML_Parser rootParser = getRootParserOf(parser, NULL); ++ assert(! rootParser->m_parentParser); ++ ++ return rootParser->m_hash_secret_salt; + } + + static enum XML_Error +@@ -2321,12 +2322,14 @@ int XMLCALL + XML_SetHashSalt(XML_Parser parser, unsigned long hash_salt) { + if (parser == NULL) + return 0; +- if (parser->m_parentParser) +- return XML_SetHashSalt(parser->m_parentParser, hash_salt); ++ ++ const XML_Parser rootParser = getRootParserOf(parser, NULL); ++ assert(! rootParser->m_parentParser); ++ + /* block after XML_Parse()/XML_ParseBuffer() has been called */ +- if (parserBusy(parser)) ++ if (parserBusy(rootParser)) + return 0; +- parser->m_hash_secret_salt = hash_salt; ++ rootParser->m_hash_secret_salt = hash_salt; + return 1; + } + diff --git a/meta/recipes-core/expat/expat/CVE-2026-41080-02.patch b/meta/recipes-core/expat/expat/CVE-2026-41080-02.patch new file mode 100644 index 00000000000..953f93c68a9 --- /dev/null +++ b/meta/recipes-core/expat/expat/CVE-2026-41080-02.patch @@ -0,0 +1,29 @@ +From 7fb2c7a454edc9e2880073a27f899c31d9b078ce Mon Sep 17 00:00:00 2001 +From: Atrem Borovik +Date: Sat, 20 Dec 2025 13:22:16 +0300 +Subject: [PATCH 2/3] WASI: remove getpid + +CVE: CVE-2026-41080 +Upstream-Status: Backport [https://github.com/libexpat/libexpat/commit/7fb2c7a454edc9e2880073a27f899c31d9b078ce] +Signed-off-by: Peter Marko +--- + lib/xmlparse.c | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/lib/xmlparse.c b/lib/xmlparse.c +index cb25c37b..1bafb948 100644 +--- a/lib/xmlparse.c ++++ b/lib/xmlparse.c +@@ -1228,8 +1228,11 @@ generate_hash_secret_salt(XML_Parser parser) { + # endif /* ! defined(_WIN32) && defined(XML_DEV_URANDOM) */ + /* .. and self-made low quality for backup: */ + ++ entropy = gather_time_entropy(); ++# if ! defined(__wasi__) + /* Process ID is 0 bits entropy if attacker has local access */ +- entropy = gather_time_entropy() ^ getpid(); ++ entropy ^= getpid(); ++# endif + + /* Factors are 2^31-1 and 2^61-1 (Mersenne primes M31 and M61) */ + if (sizeof(unsigned long) == 4) { diff --git a/meta/recipes-core/expat/expat/CVE-2026-41080-03.patch b/meta/recipes-core/expat/expat/CVE-2026-41080-03.patch new file mode 100644 index 00000000000..4d17f1a0b0e --- /dev/null +++ b/meta/recipes-core/expat/expat/CVE-2026-41080-03.patch @@ -0,0 +1,467 @@ +From b77ab600e1893fdcfc3868d0a46efcc87c87943d Mon Sep 17 00:00:00 2001 +From: Sebastian Pipping +Date: Wed, 8 Apr 2026 15:41:54 +0200 +Subject: [PATCH 3/3] [CVE-2026-41080] Improve protection against hash flooding + (fixes #47) + +Fixes #47 + +CVE: CVE-2026-41080 +Upstream-Status: Backport [https://github.com/libexpat/libexpat/pull/1183] +Signed-off-by: Peter Marko +--- + Changes | 16 ++++++ + doc/reference.html | 51 ++++++++++++++-- + lib/expat.h | 12 ++++ + lib/internal.h | 2 + + lib/xmlparse.c | 118 ++++++++++++++++++++++++++------------ + tests/basic_tests.c | 25 ++++++++ + 6 files changed, 181 insertions(+), 43 deletions(-) + +diff --git a/Changes b/Changes +index 4265d608..1d87d6a0 100644 +--- a/Changes ++++ b/Changes +@@ -30,6 +30,22 @@ + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + Patches: ++ 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: + #1018 #1034 CVE-2025-59375 -- Disallow use of disproportional amounts of + dynamic memory from within an Expat parser (e.g. previously +diff --git a/doc/reference.html b/doc/reference.html +index 8f14b011..7f374f84 100644 +--- a/doc/reference.html ++++ b/doc/reference.html +@@ -174,7 +174,8 @@ interface.

+
  • XML_GetAttributeInfo
  • +
  • XML_SetEncoding
  • +
  • XML_SetParamEntityParsing
  • +-
  • XML_SetHashSalt
  • ++
  • XML_SetHashSalt (deprecated)
  • ++
  • XML_SetHashSalt16Bytes
  • +
  • XML_UseForeignDTD
  • +
  • XML_SetReturnNSTriplet
  • +
  • XML_DefaultCurrent
  • +@@ -2553,10 +2554,10 @@ The choices for code are: + no effect and will always return 0. + + +-

    XML_SetHashSalt

    ++

    XML_SetHashSalt (deprecated)

    +
    + int XMLCALL
    +-XML_SetHashSalt(XML_Parser p,
    ++XML_SetHashSalt(XML_Parser parser,
    +                 unsigned long hash_salt);
    + 
    +
    +@@ -2564,15 +2565,55 @@ Sets the hash salt to use for internal hash calculations. + Helps in preventing DoS attacks based on predicting hash + function behavior. In order to have an effect this must be called + before parsing has started. Returns 1 if successful, 0 when called +-after XML_Parse or XML_ParseBuffer. ++after XML_Parse or XML_ParseBuffer or when ++ parser is NULL. ++

    ++ Note: Function XML_SetHashSalt is ++ deprecated. Please use function XML_SetHashSalt16Bytes instead for better ++ security. XML_SetHashSalt only provides 4 to 8 bytes of entropy ++ (depending on the size of type unsigned long) while the SipHash ++ implementation used by Expat can leverage up to 16 bytes of entropy — at least ++ twice as much. Function XML_SetHashSalt16Bytes of Expat >=2.7.6 ++ (and where backported) matches the amount of entropy supported by SipHash. ++

    . +

    Note: This call is optional, as the parser will auto-generate +-a new random salt value if no value has been set at the start of parsing.

    ++a new random salt value internally if no value has been set by the start of parsing.

    +

    Note: One should not call XML_SetHashSalt with a + hash salt value of 0, as this value is used as sentinel value to indicate + that XML_SetHashSalt has not been called. Consequently + such a call will have no effect, even if it returns 1.

    +
    + ++

    ++ XML_SetHashSalt16Bytes ++

    ++ ++
    ++/* Added in Expat 2.7.6. */
    ++XML_Bool XMLCALL
    ++XML_SetHashSalt16Bytes(XML_Parser parser,
    ++                       const uint8_t entropy[16]);
    ++
    ++
    ++ Sets the hash salt to use for internal hash calculations. Helps in preventing DoS ++ attacks based on predicting hash function behavior. In order to have an effect ++ this must be called before parsing has started. Returns XML_TRUE if ++ successful, XML_FALSE when called after XML_Parse or ++ XML_ParseBuffer or when parser is NULL. ++

    ++ Note: Setting a salt that is not from a source of high quality ++ entropy (like getentropy(3)) will make the parser vulnerable to ++ hash flooding attacks. ++

    ++ ++

    ++ Note: This call is optional, as the parser will auto-generate a new ++ random salt value internally if no value has been set by the start of parsing. ++

    ++
    ++ +

    XML_UseForeignDTD

    +
    + enum XML_Error XMLCALL
    +diff --git a/lib/expat.h b/lib/expat.h
    +index df207e9e..b356e002 100644
    +--- a/lib/expat.h
    ++++ b/lib/expat.h
    +@@ -44,6 +44,7 @@
    + #ifndef Expat_INCLUDED
    + #define Expat_INCLUDED 1
    + 
    ++#  include  // for uint8_t
    + #include 
    + #include "expat_external.h"
    + 
    +@@ -916,10 +917,21 @@ XML_SetParamEntityParsing(XML_Parser parser,
    +    function behavior. This must be called before parsing is started.
    +    Returns 1 if successful, 0 when called after parsing has started.
    +    Note: If parser == NULL, the function will do nothing and return 0.
    ++   DEPRECATED since Expat 2.7.6.
    + */
    + XMLPARSEAPI(int)
    + XML_SetHashSalt(XML_Parser parser, unsigned long hash_salt);
    + 
    ++/* Sets the hash salt to use for internal hash calculations.
    ++   Helps in preventing DoS attacks based on predicting hash function behavior.
    ++   This must be called before parsing is started.
    ++   Returns XML_TRUE if successful, XML_FALSE when called after parsing has
    ++   started or when parser is NULL.
    ++   Added in Expat 2.7.6.
    ++*/
    ++XMLPARSEAPI(XML_Bool)
    ++XML_SetHashSalt16Bytes(XML_Parser parser, const uint8_t entropy[16]);
    ++
    + /* If XML_Parse or XML_ParseBuffer have returned XML_STATUS_ERROR, then
    +    XML_GetErrorCode returns information about the error.
    + */
    +diff --git a/lib/internal.h b/lib/internal.h
    +index 32faaa05..617d6454 100644
    +--- a/lib/internal.h
    ++++ b/lib/internal.h
    +@@ -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
    +diff --git a/lib/xmlparse.c b/lib/xmlparse.c
    +index 1bafb948..75a7e5d0 100644
    +--- a/lib/xmlparse.c
    ++++ b/lib/xmlparse.c
    +@@ -604,7 +604,7 @@ static ELEMENT_TYPE *getElementType(XML_Parser parser, const ENCODING *enc,
    + 
    + 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;
    +@@ -1189,69 +1190,65 @@ 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));
    ++    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;
    ++  return entropy_128;
    + }
    + 
    +-static unsigned long
    +-generate_hash_secret_salt(XML_Parser parser) {
    +-  unsigned long entropy;
    +-  (void)parser;
    ++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 = gather_time_entropy();
    ++  entropy.k[0] = 0;
    ++  entropy.k[1] = gather_time_entropy();
    + #  if ! defined(__wasi__)
    +   /* Process ID is 0 bits entropy if attacker has local access */
    +-  entropy ^= getpid();
    ++  entropy.k[1] ^= getpid();
    + #  endif
    + 
    +   /* 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) {
    +-  const XML_Parser rootParser = getRootParserOf(parser, NULL);
    +-  assert(! rootParser->m_parentParser);
    +-
    +-  return rootParser->m_hash_secret_salt;
    +-}
    +-
    + static enum XML_Error
    + callProcessor(XML_Parser parser, const char *start, const char *end,
    +               const char **endPtr) {
    +@@ -1320,8 +1316,10 @@ callProcessor(XML_Parser parser, const char *start, const char *end,
    + 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
    +@@ -1609,7 +1607,9 @@ parserInit(XML_Parser parser, const XML_Char *encodingName) {
    +   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));
    +@@ -1776,7 +1776,8 @@ XML_ExternalEntityParserCreate(XML_Parser oldParser, const XML_Char *context,
    +      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 */
    +@@ -1822,7 +1823,8 @@ XML_ExternalEntityParserCreate(XML_Parser oldParser, const XML_Char *context,
    +      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
    +@@ -1877,7 +1879,8 @@ XML_ExternalEntityParserCreate(XML_Parser oldParser, const XML_Char *context,
    +     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
    +@@ -2321,6 +2324,7 @@ XML_SetParamEntityParsing(XML_Parser parser,
    + #endif
    + }
    + 
    ++// DEPRECATED since Expat 2.7.6.
    + int XMLCALL
    + XML_SetHashSalt(XML_Parser parser, unsigned long hash_salt) {
    +   if (parser == NULL)
    +@@ -2332,10 +2336,46 @@ XML_SetHashSalt(XML_Parser parser, unsigned long hash_salt) {
    +   /* block after XML_Parse()/XML_ParseBuffer() has been called */
    +   if (parserBusy(rootParser))
    +     return 0;
    +-  rootParser->m_hash_secret_salt = hash_salt;
    ++
    ++  rootParser->m_hash_secret_salt_128.k[0] = 0;
    ++  rootParser->m_hash_secret_salt_128.k[1] = hash_salt;
    ++
    ++  if (hash_salt != 0) { // to remain backwards compatible
    ++    rootParser->m_hash_secret_salt_set = XML_TRUE;
    ++
    ++    if (sizeof(unsigned long) == 4)
    ++      ENTROPY_DEBUG("explicit(4)", rootParser->m_hash_secret_salt_128);
    ++    else
    ++      ENTROPY_DEBUG("explicit(8)", rootParser->m_hash_secret_salt_128);
    ++  }
    ++
    +   return 1;
    + }
    + 
    ++XML_Bool XMLCALL
    ++XML_SetHashSalt16Bytes(XML_Parser parser, const uint8_t entropy[16]) {
    ++  if (parser == NULL)
    ++    return XML_FALSE;
    ++
    ++  if (entropy == NULL)
    ++    return XML_FALSE;
    ++
    ++  const XML_Parser rootParser = getRootParserOf(parser, NULL);
    ++  assert(! rootParser->m_parentParser);
    ++
    ++  /* block after XML_Parse()/XML_ParseBuffer() has been called */
    ++  if (parserBusy(rootParser))
    ++    return XML_FALSE;
    ++
    ++  sip_tokey(&(rootParser->m_hash_secret_salt_128), entropy);
    ++
    ++  rootParser->m_hash_secret_salt_set = XML_TRUE;
    ++
    ++  ENTROPY_DEBUG("explicit(16)", rootParser->m_hash_secret_salt_128);
    ++
    ++  return XML_TRUE;
    ++}
    ++
    + enum XML_Status XMLCALL
    + XML_Parse(XML_Parser parser, const char *s, int len, int isFinal) {
    +   if ((parser == NULL) || (len < 0) || ((s == NULL) && (len != 0))) {
    +@@ -7837,8 +7877,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
    +diff --git a/tests/basic_tests.c b/tests/basic_tests.c
    +index 023d9ce4..380caf19 100644
    +--- a/tests/basic_tests.c
    ++++ b/tests/basic_tests.c
    +@@ -204,6 +204,30 @@ START_TEST(test_hash_collision) {
    + END_TEST
    + #undef COLLIDING_HASH_SALT
    + 
    ++START_TEST(test_hash_salt_setter) {
    ++  const uint8_t entropy[16] = {'0', '1', '2', '3', '4', '5', '6', '7',
    ++                               '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
    ++  XML_Parser parser = XML_ParserCreate(NULL);
    ++
    ++  // NULL parser should be rejected
    ++  assert_true(XML_SetHashSalt16Bytes(NULL, entropy) == XML_FALSE);
    ++
    ++  // NULL entropy should be rejected
    ++  assert_true(XML_SetHashSalt16Bytes(parser, NULL) == XML_FALSE);
    ++
    ++  // Setting should be allowed more than once
    ++  assert_true(XML_SetHashSalt16Bytes(parser, entropy) == XML_TRUE);
    ++  assert_true(XML_SetHashSalt16Bytes(parser, entropy) == XML_TRUE);
    ++
    ++  // But not after parsing has started
    ++  assert_true(XML_Parse(parser, "", 0, XML_FALSE /* isFinal */)
    ++              == XML_STATUS_OK);
    ++  assert_true(XML_SetHashSalt16Bytes(parser, entropy) == XML_FALSE);
    ++
    ++  XML_ParserFree(parser);
    ++}
    ++END_TEST
    ++
    + /* Regression test for SF bug #491986. */
    + START_TEST(test_danish_latin1) {
    +   const char *text = "\n"
    +@@ -6244,6 +6268,7 @@ make_basic_test_case(Suite *s) {
    +   tcase_add_test(tc_basic, test_bom_utf16_le);
    +   tcase_add_test(tc_basic, test_nobom_utf16_le);
    +   tcase_add_test(tc_basic, test_hash_collision);
    ++  tcase_add_test(tc_basic, test_hash_salt_setter);
    +   tcase_add_test(tc_basic, test_illegal_utf8);
    +   tcase_add_test(tc_basic, test_utf8_auto_align);
    +   tcase_add_test(tc_basic, test_utf16);
    diff --git a/meta/recipes-core/expat/expat_2.6.4.bb b/meta/recipes-core/expat/expat_2.6.4.bb
    index 151720a9e3c..f5c3e3fdd2f 100644
    --- a/meta/recipes-core/expat/expat_2.6.4.bb
    +++ b/meta/recipes-core/expat/expat_2.6.4.bb
    @@ -51,6 +51,9 @@ SRC_URI = "${GITHUB_BASE_URI}/download/R_${VERSION_TAG}/expat-${PV}.tar.bz2  \
                file://CVE-2026-32777-02.patch \
                file://CVE-2026-32778-01.patch \
                file://CVE-2026-32778-02.patch \
    +           file://CVE-2026-41080-01.patch \
    +           file://CVE-2026-41080-02.patch \
    +           file://CVE-2026-41080-03.patch \
                "
     
     GITHUB_BASE_URI = "https://github.com/libexpat/libexpat/releases/"