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.

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 a single 32-bit value:
ct->hash_basis.  That basis was previously drawn once from
random_uint32() at conntrack_init() time.  Because it is a single,
long-lived value on which every NAT address and default port choice
depends, 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, could
predict every NAT tuple ovs-vswitchd will assign.  In effect, it was
derived from a shared, reversible state.

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.

Signed-off-by: Aaron Conole <[email protected]>
---
 lib/conntrack.c | 17 ++++++++++++++---
 1 file changed, 14 insertions(+), 3 deletions(-)

diff --git a/lib/conntrack.c b/lib/conntrack.c
index f84cdd216a..9b322c93ea 100644
--- a/lib/conntrack.c
+++ b/lib/conntrack.c
@@ -55,6 +55,17 @@ COVERAGE_DEFINE(conntrack_l4csum_err);
 COVERAGE_DEFINE(conntrack_lookup_natted_miss);
 COVERAGE_DEFINE(conntrack_zone_full);
 
+/* NAT tuple selection (both the address/port hash basis and the
+ * fully-random port paths) must not be predictable from an observed
+ * random_uint32() output, so it must draw from a cryptographic
+ * source here instead of the general-purpose xorshift32 PRNG in
+ * random.c. */
+static uint32_t
+nat_random_uint32(void)
+{
+    return cs_random_uint32();
+}
+
 struct conn_lookup_ctx {
     struct conn_key key;
     struct conn *conn;
@@ -252,7 +263,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 = nat_random_uint32();
 
     ovs_rwlock_init(&ct->resources_lock);
     ovs_rwlock_wrlock(&ct->resources_lock);
@@ -2557,7 +2568,7 @@ another_round:
 
     if (attempts < range && attempts >= 16) {
         attempts /= 2;
-        curr = min + (random_uint32() % range);
+        curr = min + (nat_random_uint32() % range);
         goto another_round;
     }
 
@@ -2613,7 +2624,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 = nat_random_uint32();
     } else if (basis) {
         port_off = hash;
     } else {
-- 
2.55.0

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to