BUG/MEDIUM: tools: insert an XXH64 layer on the PRNG output

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.
This commit is contained in:
Willy Tarreau 2026-05-25 17:30:58 +02:00
parent e8c9aabd62
commit de266d9a99
2 changed files with 23 additions and 1 deletions

View 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()
{

View 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,