From: Timo Sirainen Date: Mon, 13 Apr 2026 10:38:50 +0000 (+0200) Subject: [PATCH 04/12] lib: Key str_hash/strcase_hash with a random seed to prevent HashDoS X-Git-Tag: archive/raspbian/1%2.4.1+dfsg1-6+rpi1+deb13u7^2~37 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=fe785bf32e7cb61c7a9944f6eac9c55e54ec2f20;p=dovecot.git [PATCH 04/12] lib: Key str_hash/strcase_hash with a random seed to prevent HashDoS hash_init() fills a process-wide uint64_t hash_iv via random_fill() at lib_init() time (after random_init()). str_hash() and strcase_hash() pass hash_iv as the xxh64 seed so an attacker cannot predict bucket placement and manufacture collision chains. Gbp-Pq: Name 0004-lib-Key-str_hash-strcase_hash-with-a-random-seed-to-.patch --- diff --git a/src/lib/hash.c b/src/lib/hash.c index 61fe86e..83eeed4 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -5,6 +5,7 @@ #include "lib.h" #include "hash.h" #include "primes.h" +#include "randgen.h" #include "xxh64.h" #include @@ -59,6 +60,8 @@ enum hash_table_operation{ HASH_TABLE_OP_RESIZE }; +uint64_t hash_iv; + static bool hash_table_resize(struct hash_table *table, bool grow); void hash_table_create(struct hash_table **table_r, pool_t node_pool, @@ -520,9 +523,14 @@ void hash_table_copy(struct hash_table *dest, struct hash_table *src) hash_table_thaw(dest); } +void hash_init(void) +{ + random_fill(&hash_iv, sizeof(hash_iv)); +} + unsigned int str_hash(const char *p) { - return xxh64_to_32(xxh64_data(p, strlen(p), 0)); + return xxh64_to_32(xxh64_data(p, strlen(p), hash_iv)); } unsigned int str_stable_hash(const char *p) @@ -535,7 +543,7 @@ unsigned int strcase_hash(const char *p) struct xxh64_context ctx; unsigned char c; - xxh64_init(&ctx, 0); + xxh64_init(&ctx, hash_iv); while (*p != '\0') { c = (unsigned char)i_toupper(*p++); xxh64_loop(&ctx, &c, 1); diff --git a/src/lib/hash.h b/src/lib/hash.h index 84d9c52..534c25f 100644 --- a/src/lib/hash.h +++ b/src/lib/hash.h @@ -14,6 +14,9 @@ typedef unsigned int hash_callback_t(const void *p); /* Returns 0 if the pointers are equal. */ typedef int hash_cmp_callback_t(const void *p1, const void *p2); +/* Random per-process IV to use for hash functions */ +extern uint64_t hash_iv; + /* Create a new hash table. If initial_size is 0, the default value is used. table_pool is used to allocate/free large hash tables, node_pool is used for smaller allocations and can also be alloconly pool. The pools must not @@ -168,9 +171,12 @@ void hash_table_copy(struct hash_table *dest, struct hash_table *src); #define hash_table_copy(table1, table2) \ hash_table_copy((table1)._table, (table2)._table) -/* hash function for strings */ +/* Hash function for strings. These are safe against collision attacks. They + are initialized with a per-process random key. */ unsigned int str_hash(const char *p) ATTR_PURE; unsigned int strcase_hash(const char *p) ATTR_PURE; +/* Like str_hash(), but there is no per-process random key. This isn't as safe + against collision attacks. */ unsigned int str_stable_hash(const char *p) ATTR_PURE; /* fast hash function which uppercases a-z. Does not work well @@ -181,4 +187,6 @@ unsigned int strfastcase_hash(const char *p) ATTR_PURE; /* a generic hash for a given memory block */ unsigned int mem_hash(const void *p, unsigned int size) ATTR_PURE; +void hash_init(void); + #endif diff --git a/src/lib/lib.c b/src/lib/lib.c index 4df939b..4a4372f 100644 --- a/src/lib/lib.c +++ b/src/lib/lib.c @@ -5,6 +5,7 @@ #include "array.h" #include "event-filter.h" #include "env-util.h" +#include "hash.h" #include "hostpid.h" #include "ipwd.h" #include "process-title.h" @@ -184,6 +185,7 @@ void lib_init(void) { i_assert(!lib_initialized); random_init(); + hash_init(); data_stack_init(); hostpid_init(); lib_open_non_stdio_dev_null();