Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
311 changes: 311 additions & 0 deletions SPECS/expat/CVE-2026-41080.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,311 @@
From 2ce1cae891d995481b913e16ea54870a34c0e009 Mon Sep 17 00:00:00 2001
From: Sebastian Pipping <sebastian@pipping.org>
Date: Wed, 8 Apr 2026 15:41:54 +0200
Subject: [PATCH] lib: Inline function `get_hash_secret_salt`

Upstream Patch Link: https://patch-diff.githubusercontent.com/raw/libexpat/libexpat/pull/1183.patch
---
lib/expat.h | 12 +++++
lib/internal.h | 2 +
lib/xmlparse.c | 123 ++++++++++++++++++++++++++++++++++---------------
3 files changed, 100 insertions(+), 37 deletions(-)

diff --git a/lib/expat.h b/lib/expat.h
index df207e9..b356e00 100644
--- a/lib/expat.h
+++ b/lib/expat.h
@@ -44,6 +44,7 @@
#ifndef Expat_INCLUDED
#define Expat_INCLUDED 1

+# include <stdint.h> // for uint8_t
#include <stdlib.h>
#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 1b763ff..7aea199 100644
--- a/lib/internal.h
+++ b/lib/internal.h
@@ -112,6 +112,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"
@@ -121,6 +122,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 3cd8ff1..7d0de12 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;
@@ -1183,65 +1184,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.k[0] = 0;
+ entropy.k[1] = gather_time_entropy();
+# if ! defined(__wasi__)
/* Process ID is 0 bits entropy if attacker has local access */
- entropy = gather_time_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) {
- 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) {
@@ -1310,8 +1311,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
@@ -1598,7 +1601,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));
@@ -1765,7 +1770,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 */
@@ -1811,7 +1817,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
@@ -1866,7 +1873,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
@@ -2310,19 +2318,58 @@ 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)
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))
return 0;
- parser->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))) {
@@ -7825,8 +7872,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.45.4

Loading
Loading