]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
BUG/MINOR: init: use more than ha_random64() for the cluster secret
authorWilly Tarreau <w@1wt.eu>
Sun, 24 May 2026 09:06:31 +0000 (11:06 +0200)
committerWilly Tarreau <w@1wt.eu>
Mon, 25 May 2026 08:52:42 +0000 (10:52 +0200)
When not set, the cluster secret is randomly generated by two
consecutive calls to ha_random64(). However, the random64 PRNG may be
partially observed on a fully idle machine (QUIC retry tokens, UUID,
WS key), and it could be rolled back to the initial call that produced
the secret. This is purely theoretical as a normally loaded system
wouldn't reveal meaningful sequences, but better address this while
it's still easy.

The first here consists in isolating the cluster_secret from the PRNG
sequence. When RAND_bytes() is available and works, it's used. Otherwise
ha_random64() is mixed with uncorrelated bits from random().

This could be backported to stable releases.

src/haproxy.c

index 560bc1e070cd3152026352f0244dbeb589aca55c..51d1fc0de5d37158e06c15a64b202ea297d9e0b2 100644 (file)
@@ -1926,20 +1926,37 @@ static void dump_registered_keywords(void)
 
 /* Generate a random cluster-secret in case the setting is not provided in the
  * configuration. This allows to use features which rely on it albeit with some
- * limitations.
+ * limitations. The function doesn't (solely) use ha_random64() because this
+ * secret is permanent, and ha_random64() can easily be leaked at various
+ * places.
  */
 static void generate_random_cluster_secret()
 {
        /* used as a default random cluster-secret if none defined. */
-       uint64_t rand;
+       union {
+               uint64_t by64[2];
+               uint32_t by32[4];
+               uchar    by8[16];
+       } rand;
 
        /* The caller must not overwrite an already defined secret. */
        BUG_ON(cluster_secret_isset);
+       BUG_ON(sizeof(global.cluster_secret) != sizeof(rand));
+
+#ifdef USE_OPENSSL
+       if (RAND_bytes(rand.by8, sizeof(rand.by8)) != 1)
+#endif
+       {
+               /* no SSL or not working, fall back to other sources */
+               rand.by64[0] = ha_random64();
+               rand.by64[1] = ha_random64();
+               rand.by32[0] ^= ((random() & 0x00ffff00) << 8) | ((random() & 0x00ffff00) >> 8);
+               rand.by32[1] ^= ((random() & 0x00ffff00) << 8) | ((random() & 0x00ffff00) >> 8);
+               rand.by32[2] ^= ((random() & 0x00ffff00) << 8) | ((random() & 0x00ffff00) >> 8);
+               rand.by32[3] ^= ((random() & 0x00ffff00) << 8) | ((random() & 0x00ffff00) >> 8);
+       }
 
-       rand = ha_random64();
        memcpy(global.cluster_secret, &rand, sizeof(rand));
-       rand = ha_random64();
-       memcpy(global.cluster_secret + sizeof(rand), &rand, sizeof(rand));
        cluster_secret_isset = 1;
 }