]> dgit.raspbian.org Git - dovecot.git/commitdiff
[PATCH 04/12] lib: Key str_hash/strcase_hash with a random seed to prevent HashDoS
authorTimo Sirainen <timo.sirainen@open-xchange.com>
Mon, 13 Apr 2026 10:38:50 +0000 (12:38 +0200)
committerNoah Meyerhans <noahm@debian.org>
Wed, 16 Sep 2026 19:06:35 +0000 (15:06 -0400)
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

src/lib/hash.c
src/lib/hash.h
src/lib/lib.c

index 61fe86ed8e84835e50c42871d2f8eacc977a3052..83eeed42f648280ea27a3127629d3fe1b3a6ea51 100644 (file)
@@ -5,6 +5,7 @@
 #include "lib.h"
 #include "hash.h"
 #include "primes.h"
+#include "randgen.h"
 #include "xxh64.h"
 
 #include <ctype.h>
@@ -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);
index 84d9c52aefa1a97e9fd1e20d38948f6346282bd8..534c25f6eed4cb34054c7306d365d718de6cf57a 100644 (file)
@@ -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
index 4df939b7a19bdf23685782f259e6e342bc544b20..4a4372f516b1ebe9cb26e519904d039faf3b9680 100644 (file)
@@ -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();