random_uint32() is a xorshift32 PRNG (see lib/random.c): it holds many properties including uniformity and reversibility, so recovering the internal state from any single observed output lets an attacker compute every other output of that (per-thread) stream, forward and backward. Using it for anything security-relevant is documented in random.c itself as inappropriate.
ct->hash_basis salts every conntrack hash table, to include connection, zone, timeout policy and ALG expectation lookups and it is also the secret input to NAT tuple selection. Most NAT tuple selection does not call random_uint32() directly. Instead, nat_get_unique_tuple() picks both the NAT address (get_addr_in_range()) and, in the common case, the NAT port (set_sport_range()/set_dport_range()) via nat_range_hash(), a deterministic hash of the flow tuple mixed with that single 32-bit value, which was previously drawn once from random_uint32() at conntrack_init() time. An attacker able to recover the xorshift state feeding that one random_uint32() call, e.g. by observing any other output drawn from the same per-thread PRNG stream elsewhere in the process, would learn the basis and could then predict every NAT tuple ovs-vswitchd assigns for the lifetime of the process. Two narrower paths draw directly from random_uint32() per new connection rather than through the hash: the 'nat(...,random)' port path, and the retry offset used when the hash-selected port collides with an existing connection under range congestion. These are directly observable per-connection outputs. Fix this by drawing ct->hash_basis, and the two per-connection port paths above, from a cryptographic source: the recently added CSPRNG support in random.c which is a ChaCha20 based CSPRNG. Conntrack randomness is then independent of every other random_uint32() consumer, and the per-connection draws are no longer part of a reversible stream. A zero basis is redrawn because nat_get_unique_tuple() reads it as the NAT_PERSISTENT case. This does not, by itself, make hash-selected tuples unpredictable: nat_range_hash() has only the 32-bit basis as a secret, so an attacker observing a few translated tuples can still search for it offline. Hardening the selection function against that is a separate problem. Assisted-by: LLM (claude, opencode, sonnet, opus, qwen3.8) Signed-off-by: Aaron Conole <[email protected]> --- v2->v3: * Rename the nat_random_uint32() function to ct_random_uint32() * Document the narrower case this solves (just fixes the nat(random) case rather than claiming the entire range predictability). lib/conntrack.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/lib/conntrack.c b/lib/conntrack.c index f84cdd216a..7c1001eb58 100644 --- a/lib/conntrack.c +++ b/lib/conntrack.c @@ -55,6 +55,23 @@ COVERAGE_DEFINE(conntrack_l4csum_err); COVERAGE_DEFINE(conntrack_lookup_natted_miss); COVERAGE_DEFINE(conntrack_zone_full); +/* Conntrack salts its hash tables with a random value. That value, + * and the fully-random NAT port paths, must not be predictable from + * an observed random_uint32() output. Because nat_get_unique_tuple() + * interprets a zero value as the NAT_PERSISTENT case this never + * returns 0. */ +static uint32_t +ct_random_uint32(void) +{ + uint32_t value; + + do { + value = cs_random_uint32(); + } while (!value); + + return value; +} + struct conn_lookup_ctx { struct conn_key key; struct conn *conn; @@ -252,7 +269,7 @@ conntrack_init(void) /* This value can be used during init (e.g. timeout_policy_init()), * set it first to ensure it is available. */ - ct->hash_basis = random_uint32(); + ct->hash_basis = ct_random_uint32(); ovs_rwlock_init(&ct->resources_lock); ovs_rwlock_wrlock(&ct->resources_lock); @@ -2557,7 +2574,7 @@ another_round: if (attempts < range && attempts >= 16) { attempts /= 2; - curr = min + (random_uint32() % range); + curr = min + (ct_random_uint32() % range); goto another_round; } @@ -2613,7 +2630,7 @@ nat_get_unique_tuple(struct conntrack *ct, struct conn *conn, hash = nat_range_hash(fwd_key, basis, nat_info); if (nat_info->nat_flags & NAT_RANGE_RANDOM) { - port_off = random_uint32(); + port_off = ct_random_uint32(); } else if (basis) { port_off = hash; } else { -- 2.56.0 _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
