]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
BUG/MEDIUM: tools: insert an XXH64 layer on the PRNG output
authorWilly Tarreau <w@1wt.eu>
Mon, 25 May 2026 15:30:58 +0000 (17:30 +0200)
committerWilly Tarreau <w@1wt.eu>
Tue, 26 May 2026 11:13:24 +0000 (13:13 +0200)
Consuming randoms in pairs directly exposes the internal PRNG's state
on moderately idle system. It can allow to predict next (or previous)
UUIDs, QUIC retry tokens, and WS keys for example. Let's insert an XXH64
call on the ha_random64() output to avoid this. We expand the boot seed
as the secret at boot, and use now_ns as the seed for each call. The
original ha_random64() function was renamed to ha_random64_internal()
for use cases where it's not a problem to directly use the internal
state.

The performance loss is only measurable when single-threaded. It drops
from 7.32M UUID per second to 7.16M. Above that there is no longer any
difference due to the DWCAS loop which reaches up to 98.5% CPU at 20
threads.

This will need to be backported to stable releases after a period of
observation.

include/haproxy/tools.h
src/tools.c

index 883bba59723e63e0dda271adc69138fbb19375a1..6e47585098fa53ebeaf2bf9b09d7c26631223b32 100644 (file)
@@ -1293,6 +1293,7 @@ void ha_generate_uuid_v7(struct buffer *output);
 void ha_random_seed(const unsigned char *seed, size_t len);
 void ha_random_jump96(uint32_t dist);
 uint64_t ha_random64(void);
+uint64_t ha_random64_internal(void);
 
 static inline uint32_t ha_random32()
 {
index 23a865a8c458f97a792aa7527c403cf558c5dad7..578d38d1f0da8a212be8e25c7da6cd646d066db4 100644 (file)
@@ -6235,6 +6235,8 @@ int varint_bytes(uint64_t v)
        return len;
 }
 
+/* secret used for XXH hash involved in PRNG */
+static char ha_random_xxh_secret[XXH3_SECRET_DEFAULT_SIZE] ALIGNED(64);
 
 /* Random number generator state, see below */
 static uint64_t ha_random_state[2] ALIGNED(2*sizeof(uint64_t));
@@ -6245,8 +6247,10 @@ static uint64_t ha_random_state[2] ALIGNED(2*sizeof(uint64_t));
  * supports fast jumps and passes all common quality tests. It is thread-safe,
  * uses a double-cas on 64-bit architectures supporting it, and falls back to a
  * local lock on other ones.
+ * It may only be used for internal random generation, because exposing its
+ * output will quickly reveal the internal state.
  */
-uint64_t ha_random64()
+uint64_t ha_random64_internal()
 {
        uint64_t old[2] ALIGNED(2*sizeof(uint64_t));
        uint64_t new[2] ALIGNED(2*sizeof(uint64_t));
@@ -6279,6 +6283,20 @@ uint64_t ha_random64()
        return rotl64(old[0] * 5, 7) * 9;
 }
 
+/* Returns a uint64_t random hashed so as not to disclose the internal PRNG
+ * state. The function uses a local XXH secret that is created at boot, and
+ * now_ns as the seed to limit remote analysis.
+ */
+uint64_t ha_random64(void)
+{
+       uint64_t ret;
+
+       ret = ha_random64_internal();
+       return XXH3_64bits_withSecretandSeed(&ret, sizeof(ret),
+                                            ha_random_xxh_secret, sizeof(ha_random_xxh_secret),
+                                            now_ns);
+}
+
 /* seeds the random state using up to <len> bytes from <seed>, starting with
  * the first non-zero byte.
  */
@@ -6306,6 +6324,9 @@ void ha_random_seed(const unsigned char *seed, size_t len)
                len = sizeof(ha_random_state);
 
        memcpy(ha_random_state, seed, len);
+
+       /* also initialize the secret table used by XXH3 */
+       XXH3_generateSecret(ha_random_xxh_secret, sizeof(ha_random_xxh_secret), seed, len);
 }
 
 /* This causes a jump to (dist * 2^96) places in the pseudo-random sequence,