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"
code are:
+ no effect and will always return 0.
+
+
+-+ int XMLCALL +-XML_SetHashSalt(XML_Parser p, ++XML_SetHashSalt(XML_Parser parser, + unsigned long hash_salt); ++
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.
++/* Added in Expat 2.7.6. */ ++XML_Bool XMLCALL ++XML_SetHashSalt16Bytes(XML_Parser parser, ++ const uint8_t entropy[16]); ++++
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. ++
+++ 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/"