From: "Yoann Congal" <yoann.congal@smile.fr>
To: <amaury.couderc@est.tech>, <openembedded-core@lists.openembedded.org>
Subject: Re: [OE-core] [PATCH 1/2][wrynose] expat: fix CVE-2026-41080
Date: Wed, 08 Jul 2026 18:22:24 +0200 [thread overview]
Message-ID: <DJTBTIVJM80J.1MHCSDMFBGO91@smile.fr> (raw)
In-Reply-To: <20260706120430.37403-1-amaury.couderc@est.tech>
On Mon Jul 6, 2026 at 2:04 PM CEST, Amaury Couderc via lists.openembedded.org wrote:
> From: Amaury Couderc <amaury.couderc@est.tech>
>
> 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.
>
> Introduces new API function XML_SetHashSalt16Bytes and deprecates the
> legacy XML_SetHashSalt which is limited to 4-8 bytes.
>
> The fix is split into 3 patches for reviewability:
> 1. Core entropy changes: migrates hash salt storage to struct sipkey,
> extracts 16 bytes of entropy, and introduces m_hash_secret_salt_set.
> 2. New API: adds XML_SetHashSalt16Bytes, deprecates XML_SetHashSalt,
> updates documentation, symbol exports, and tests.
> 3. Windows/cmake: adds the missing .def export for the new symbol.
>
> Backport patch to fix CVE-2026-41080
> https://nvd.nist.gov/vuln/detail/CVE-2026-41080
>
> Upstream fix:
> https://github.com/libexpat/libexpat/pull/1183
>
> CVE: CVE-2026-41080
>
> Signed-off-by: Amaury Couderc <amaury.couderc@est.tech>
> ---
> .../expat/expat/CVE-2026-41080-1.patch | 251 ++++++++++++++
> .../expat/expat/CVE-2026-41080-2.patch | 310 ++++++++++++++++++
> .../expat/expat/CVE-2026-41080-3.patch | 32 ++
> meta/recipes-core/expat/expat_2.7.5.bb | 3 +
> 4 files changed, 596 insertions(+)
> create mode 100644 meta/recipes-core/expat/expat/CVE-2026-41080-1.patch
> create mode 100644 meta/recipes-core/expat/expat/CVE-2026-41080-2.patch
> create mode 100644 meta/recipes-core/expat/expat/CVE-2026-41080-3.patch
>
> diff --git a/meta/recipes-core/expat/expat/CVE-2026-41080-1.patch b/meta/recipes-core/expat/expat/CVE-2026-41080-1.patch
> new file mode 100644
> index 0000000000..d2a957a5e8
> --- /dev/null
> +++ b/meta/recipes-core/expat/expat/CVE-2026-41080-1.patch
> @@ -0,0 +1,251 @@
> +From 944f825b0119257fee3aed0841e44a6d1c674f80 Mon Sep 17 00:00:00 2001
> +From: Amaury Couderc <amaury.couderc@est.tech>
> +Date: Thu, 2 Jul 2026 09:10:07 +0000
> +Subject: [PATCH 1/3] expat: fix CVE-2026-41080 use 16 bytes of entropy for hash salt
> +
> +The existing hash flooding protection in libexpat (based on SipHash)
> +only used 4 to 8 bytes of entropy for a salt, when 16 bytes are
> +supported by the SipHash implementation. This allows attackers to more
> +feasibly guess the hash salt and craft inputs that cause hash
> +collisions, leading to denial of service.
> +
> +Backport upstream changes that:
> +- Drop unused parameter from generate_hash_secret_salt
> +- Migrate hash salt storage to larger struct sipkey (128-bit)
> +- Drop unneeded void * casts in generate_hash_secret_salt
> +- Extract 16 bytes of entropy for hash flooding protection
> +- Introduce internal flag m_hash_secret_salt_set
> +- Update get_hash_secret_salt to return from the new 128-bit struct
> +
> +Squashed backport of upstream commits 909201a8, bc193afc, 08697a9b,
> +f5eacefb, and fa1ebd60.
Hello,
Please don't squash commits. It makes it harder for me to review and the
Upstream-Status below is not really true...
Look at what Peter did for scarthgap for inspiration:
https://lore.kernel.org/all/20260505205229.2139449-1-peter.marko@siemens.com/
Regards,
> +
> +Based on upstream PR: https://github.com/libexpat/libexpat/pull/1183
> +
> +CVE: CVE-2026-41080
> +Upstream-Status: Backport [https://github.com/libexpat/libexpat/pull/1183/commits/fa1ebd60bfcc6d32f329803e5e837251e2387ed1]
> +
> +Signed-off-by: Sebastian Pipping <sebastian@pipping.org>
> +Signed-off-by: Amaury Couderc <amaury.couderc@est.tech>
> +---
> + lib/internal.h | 2 ++
> + lib/xmlparse.c | 80 +++++++++++++++++++++++++++++++-------------------
> + 2 files changed, 52 insertions(+), 30 deletions(-)
> +
> +diff --git a/lib/internal.h b/lib/internal.h
> +index 61266ebb..1995c17b 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 0248b665..59235c85 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;
> +@@ -1192,57 +1193,61 @@ 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
> + }
> +@@ -1252,7 +1257,7 @@ get_hash_secret_salt(XML_Parser parser) {
> + const XML_Parser rootParser = getRootParserOf(parser, NULL);
> + assert(! rootParser->m_parentParser);
> +
> +- return rootParser->m_hash_secret_salt;
> ++ return rootParser->m_hash_secret_salt_128.k[1];
> + }
> +
> + static enum XML_Error
> +@@ -1323,8 +1328,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
> +@@ -1612,7 +1619,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));
> +@@ -1779,7 +1788,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 */
> +@@ -1825,7 +1835,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
> +@@ -1880,7 +1891,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
> +@@ -2335,7 +2347,13 @@ 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;
> ++
> + return 1;
> + }
> +
> +@@ -7842,8 +7860,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
> +--
> +2.34.1
> +
> diff --git a/meta/recipes-core/expat/expat/CVE-2026-41080-2.patch b/meta/recipes-core/expat/expat/CVE-2026-41080-2.patch
> new file mode 100644
> index 0000000000..a52d61e611
> --- /dev/null
> +++ b/meta/recipes-core/expat/expat/CVE-2026-41080-2.patch
> @@ -0,0 +1,310 @@
> +From 969472a6d33e994c234ab2ed34f1a01348351b28 Mon Sep 17 00:00:00 2001
> +From: Amaury Couderc <amaury.couderc@est.tech>
> +Date: Thu, 2 Jul 2026 09:23:00 +0000
> +Subject: [PATCH 2/3] expat: add XML_SetHashSalt16Bytes API for CVE-2026-41080
> +
> +Add the new XML_SetHashSalt16Bytes API function which accepts a full
> +16 bytes of entropy for the hash salt, matching the capacity of the
> +SipHash implementation used by Expat. The existing XML_SetHashSalt
> +function is marked as deprecated since it only provides 4 to 8 bytes
> +of entropy depending on the size of unsigned long.
> +
> +Changes include:
> +- Add XML_SetHashSalt16Bytes to public API (expat.h)
> +- Add symbol export in libexpat.map.in (LIBEXPAT_2.7.6)
> +- Mark XML_SetHashSalt as deprecated
> +- Document that XML_SetHashSalt needs a non-NULL parser
> +- Include XML_SetHashSalt* with entropy debugging
> +- Add test_hash_salt_setter unit test
> +- Update documentation and Changes file
> +
> +Squashed backport of upstream commits f76124e7, e3349d85, c8c5caf4,
> +592d5fa3, 8ad3ef57, ec9fcd2e, and 8017e11e.
> +
> +CVE: CVE-2026-41080
> +Upstream-Status: Backport [https://github.com/libexpat/libexpat/pull/1183/commits/4ba09dc471b39a78d77e5179d0243186c0c4ff7a]
> +
> +Signed-off-by: Sebastian Pipping <sebastian@pipping.org>
> +Signed-off-by: Amaury Couderc <amaury.couderc@est.tech>
> +---
> + Changes | 17 ++++++++++++++
> + doc/reference.html | 55 ++++++++++++++++++++++++++++++++++++++++-----
> + lib/expat.h | 15 +++++++++++++++
> + lib/libexpat.map.in | 5 +++++
> + lib/xmlparse.c | 33 ++++++++++++++++++++++++++-
> + tests/basic_tests.c | 25 +++++++++++++++++++++
> + 6 files changed, 144 insertions(+), 6 deletions(-)
> +
> +diff --git a/Changes b/Changes
> +index 2b3704a6..1d8227ec 100644
> +--- a/Changes
> ++++ b/Changes
> +@@ -29,6 +29,23 @@
> + !! THANK YOU! Sebastian Pipping -- Berlin, 2026-03-17 !!
> + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
> +
> ++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).
> ++
> + Release 2.7.5 Tue March 17 2026
> + Security fixes:
> + #1158 CVE-2026-32776 -- Fix NULL function pointer dereference for
> +diff --git a/doc/reference.html b/doc/reference.html
> +index 5faa8d65..64b9fd67 100644
> +--- a/doc/reference.html
> ++++ b/doc/reference.html
> +@@ -404,7 +404,11 @@
> + </li>
> +
> + <li>
> +- <a href="#XML_SetHashSalt">XML_SetHashSalt</a>
> ++ <a href="#XML_SetHashSalt">XML_SetHashSalt</a> (deprecated)
> ++ </li>
> ++
> ++ <li>
> ++ <a href="#XML_SetHashSalt16Bytes">XML_SetHashSalt16Bytes</a>
> + </li>
> +
> + <li>
> +@@ -3449,22 +3453,35 @@ XML_SetParamEntityParsing(XML_Parser p,
> + </div>
> +
> + <h4 id="XML_SetHashSalt">
> +- XML_SetHashSalt
> ++ XML_SetHashSalt (deprecated)
> + </h4>
> +
> + <pre class="fcndec">
> + int XMLCALL
> +-XML_SetHashSalt(XML_Parser p,
> ++XML_SetHashSalt(XML_Parser parser,
> + unsigned long hash_salt);
> + </pre>
> + <div class="fcndef">
> + 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 <code>XML_Parse</code> or <code>XML_ParseBuffer</code>.
> ++ called after <code>XML_Parse</code> or <code>XML_ParseBuffer</code> or when
> ++ <code>parser</code> is <code>NULL</code>.
> ++ <p>
> ++ <b>Note:</b> Function <code>XML_SetHashSalt</code> is
> ++ <strong>deprecated</strong>. Please use function <code><a href=
> ++ "#XML_SetHashSalt16Bytes">XML_SetHashSalt16Bytes</a></code> instead for better
> ++ security. <code>XML_SetHashSalt</code> only provides 4 to 8 bytes of entropy
> ++ (depending on the size of type <code>unsigned long</code>) while the SipHash
> ++ implementation used by Expat can leverage up to 16 bytes of entropy — at least
> ++ twice as much. Function <code><a href=
> ++ "#XML_SetHashSalt16Bytes">XML_SetHashSalt16Bytes</a></code> of Expat >=2.7.6
> ++ (and where backported) matches the amount of entropy supported by SipHash.
> ++ </p>
> ++
> + <p>
> + <b>Note:</b> 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.
> ++ random salt value internally if no value has been set by the start of parsing.
> + </p>
> +
> + <p>
> +@@ -3475,6 +3492,34 @@ XML_SetHashSalt(XML_Parser p,
> + </p>
> + </div>
> +
> ++ <h4 id="XML_SetHashSalt16Bytes">
> ++ XML_SetHashSalt16Bytes
> ++ </h4>
> ++
> ++ <pre class="fcndec">
> ++/* Added in Expat 2.7.6. */
> ++XML_Bool XMLCALL
> ++XML_SetHashSalt16Bytes(XML_Parser parser,
> ++ const uint8_t entropy[16]);
> ++</pre>
> ++ <div class="fcndef">
> ++ 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 <code>XML_TRUE</code> if
> ++ successful, <code>XML_FALSE</code> when called after <code>XML_Parse</code> or
> ++ <code>XML_ParseBuffer</code> or when <code>parser</code> is <code>NULL</code>.
> ++ <p>
> ++ <b>Note:</b> Setting a salt that is <em>not</em> from a source of high quality
> ++ entropy (like <code>getentropy(3)</code>) will make the parser vulnerable to
> ++ hash flooding attacks.
> ++ </p>
> ++
> ++ <p>
> ++ <b>Note:</b> 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.
> ++ </p>
> ++ </div>
> ++
> + <h4 id="XML_UseForeignDTD">
> + XML_UseForeignDTD
> + </h4>
> +diff --git a/lib/expat.h b/lib/expat.h
> +index 18dbaebd..7693f62c 100644
> +--- a/lib/expat.h
> ++++ b/lib/expat.h
> +@@ -45,6 +45,7 @@
> + #ifndef Expat_INCLUDED
> + # define Expat_INCLUDED 1
> +
> ++# include <stdint.h> // for uint8_t
> + # include <stdlib.h>
> + # include "expat_external.h"
> +
> +@@ -917,10 +918,25 @@ 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]);
> ++
> ++/* Backport feature macro: signals that XML_SetHashSalt16Bytes is available
> ++ even though XML_COMBINED_VERSION < 20800. */
> ++# define XML_BACKPORT_SET_HASH_SALT_16_BYTES 1
> ++
> + /* If XML_Parse or XML_ParseBuffer have returned XML_STATUS_ERROR, then
> + XML_GetErrorCode returns information about the error.
> + */
> +diff --git a/lib/libexpat.map.in b/lib/libexpat.map.in
> +index 52e59ed3..8527eb54 100644
> +--- a/lib/libexpat.map.in
> ++++ b/lib/libexpat.map.in
> +@@ -117,3 +117,8 @@ LIBEXPAT_2.7.2 {
> + @_EXPAT_COMMENT_DTD_OR_GE@ XML_SetAllocTrackerActivationThreshold;
> + @_EXPAT_COMMENT_DTD_OR_GE@ XML_SetAllocTrackerMaximumAmplification;
> + } LIBEXPAT_2.6.0;
> ++
> ++LIBEXPAT_2.7.6 {
> ++ global:
> ++ XML_SetHashSalt16Bytes;
> ++} LIBEXPAT_2.7.2;
> +diff --git a/lib/xmlparse.c b/lib/xmlparse.c
> +index 59235c85..09ae4f92 100644
> +--- a/lib/xmlparse.c
> ++++ b/lib/xmlparse.c
> +@@ -2336,6 +2336,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)
> +@@ -2351,12 +2352,42 @@ XML_SetHashSalt(XML_Parser parser, unsigned long 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
> ++ 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))) {
> +diff --git a/tests/basic_tests.c b/tests/basic_tests.c
> +index 02d1d5fd..26662fee 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 = "<?xml version='1.0' encoding='iso-8859-1'?>\n"
> +@@ -6292,6 +6316,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);
> +--
> +2.34.1
> +
> diff --git a/meta/recipes-core/expat/expat/CVE-2026-41080-3.patch b/meta/recipes-core/expat/expat/CVE-2026-41080-3.patch
> new file mode 100644
> index 0000000000..69a1c99fcc
> --- /dev/null
> +++ b/meta/recipes-core/expat/expat/CVE-2026-41080-3.patch
> @@ -0,0 +1,32 @@
> +From cfc4475518cd38890071b280e8619e74dd9e8722 Mon Sep 17 00:00:00 2001
> +From: Christoph Reiter <reiter.christoph@gmail.com>
> +Date: Wed, 10 Jun 2026 21:27:51 +0200
> +Subject: [PATCH 3/3] cmake|windows: add missing export for new XML_SetHashSalt16Bytes
> +
> +A new XML_SetHashSalt16Bytes symbol was added in #1183, but it
> +wasn't added to the def file, so the export is missing when building
> +libexpat on Windows with cmake.
> +
> +Add the new symbol to the .def template.
> +
> +CVE: CVE-2026-41080
> +Upstream-Status: Backport [https://github.com/libexpat/libexpat/pull/1270/commits/3cdd1df2644388aff25dd0ed7128c7bb1de1a7d8]
> +
> +Signed-off-by: Amaury Couderc <amaury.couderc@est.tech>
> +---
> + lib/libexpat.def.cmake | 2 ++
> + 1 file changed, 2 insertions(+)
> +
> +diff --git a/lib/libexpat.def.cmake b/lib/libexpat.def.cmake
> +index 9b9e22cb..948135a5 100644
> +--- a/lib/libexpat.def.cmake
> ++++ b/lib/libexpat.def.cmake
> +@@ -83,3 +83,5 @@ EXPORTS
> + ; added with version 2.7.2
> + @_EXPAT_COMMENT_DTD_OR_GE@ XML_SetAllocTrackerMaximumAmplification @72
> + @_EXPAT_COMMENT_DTD_OR_GE@ XML_SetAllocTrackerActivationThreshold @73
> ++; added with version 2.7.6
> ++ XML_SetHashSalt16Bytes @74
> +--
> +2.34.1
> +
> diff --git a/meta/recipes-core/expat/expat_2.7.5.bb b/meta/recipes-core/expat/expat_2.7.5.bb
> index 4f2578292d..1191d9a943 100644
> --- a/meta/recipes-core/expat/expat_2.7.5.bb
> +++ b/meta/recipes-core/expat/expat_2.7.5.bb
> @@ -10,6 +10,9 @@ VERSION_TAG = "${@d.getVar('PV').replace('.', '_')}"
>
> SRC_URI = "${GITHUB_BASE_URI}/download/R_${VERSION_TAG}/expat-${PV}.tar.bz2 \
> file://run-ptest \
> + file://CVE-2026-41080-1.patch \
> + file://CVE-2026-41080-2.patch \
> + file://CVE-2026-41080-3.patch \
> "
>
> GITHUB_BASE_URI = "https://github.com/libexpat/libexpat/releases/"
--
Yoann Congal
Smile ECS
prev parent reply other threads:[~2026-07-08 16:22 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-06 12:04 [PATCH 1/2][wrynose] expat: fix CVE-2026-41080 amaury.couderc
2026-07-08 16:22 ` Yoann Congal [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=DJTBTIVJM80J.1MHCSDMFBGO91@smile.fr \
--to=yoann.congal@smile.fr \
--cc=amaury.couderc@est.tech \
--cc=openembedded-core@lists.openembedded.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox