Skip to content

Commit ef56248

Browse files
committed
Merge branch 'insufficient-tcp-source-port-randomness'
Willy Tarreau says: ==================== insufficient TCP source port randomness In a not-yet published paper, Moshe Kol, Amit Klein, and Yossi Gilad report being able to accurately identify a client by forcing it to emit only 40 times more connections than the number of entries in the table_perturb[] table, which is indexed by hashing the connection tuple. The current 2^8 setting allows them to perform that attack with only 10k connections, which is not hard to achieve in a few seconds. Eric, Amit and I have been working on this for a few weeks now imagining, testing and eliminating a number of approaches that Amit and his team were still able to break or that were found to be too risky or too expensive, and ended up with the simple improvements in this series that resists to the attack, doesn't degrade the performance, and preserves a reliable port selection algorithm to avoid connection failures, including the odd/even port selection preference that allows bind() to always find a port quickly even under strong connect() stress. The approach relies on several factors: - resalting the hash secret that's used to choose the table_perturb[] entry every 10 seconds to eliminate slow attacks and force the attacker to forget everything that was learned after this delay. This already eliminates most of the problem because if a client stays silent for more than 10 seconds there's no link between the previous and the next patterns, and 10s isn't yet frequent enough to cause too frequent repetition of a same port that may induce a connection failure ; - adding small random increments to the source port. Previously, a random 0 or 1 was added every 16 ports. Now a random 0 to 7 is added after each port. This means that with the default 32768-60999 range, a worst case rollover happens after 1764 connections, and an average of 3137. This doesn't stop statistical attacks but requires significantly more iterations of the same attack to confirm a guess. - increasing the table_perturb[] size from 2^8 to 2^16, which Amit says will require 2.6 million connections to be attacked with the changes above, making it pointless to get a fingerprint that will only last 10 seconds. Due to the size, the table was made dynamic. - a few minor improvements on the bits used from the hash, to eliminate some unfortunate correlations that may possibly have been exploited to design future attack models. These changes were tested under the most extreme conditions, up to 1.1 million connections per second to one and a few targets, showing no performance regression, and only 2 connection failures within 13 billion, which is less than 2^-32 and perfectly within usual values. The series is split into small reviewable changes and was already reviewed by Amit and Eric. ==================== Link: https://lore.kernel.org/r/20220502084614.24123-1-w@1wt.eu Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2 parents 205557b + e816134 commit ef56248

5 files changed

Lines changed: 43 additions & 25 deletions

File tree

include/net/inet_hashtables.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ static inline void sk_rcv_saddr_set(struct sock *sk, __be32 addr)
425425
}
426426

427427
int __inet_hash_connect(struct inet_timewait_death_row *death_row,
428-
struct sock *sk, u32 port_offset,
428+
struct sock *sk, u64 port_offset,
429429
int (*check_established)(struct inet_timewait_death_row *,
430430
struct sock *, __u16,
431431
struct inet_timewait_sock **));

include/net/secure_seq.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44

55
#include <linux/types.h>
66

7-
u32 secure_ipv4_port_ephemeral(__be32 saddr, __be32 daddr, __be16 dport);
8-
u32 secure_ipv6_port_ephemeral(const __be32 *saddr, const __be32 *daddr,
7+
u64 secure_ipv4_port_ephemeral(__be32 saddr, __be32 daddr, __be16 dport);
8+
u64 secure_ipv6_port_ephemeral(const __be32 *saddr, const __be32 *daddr,
99
__be16 dport);
1010
u32 secure_tcp_seq(__be32 saddr, __be32 daddr,
1111
__be16 sport, __be16 dport);

net/core/secure_seq.c

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
static siphash_aligned_key_t net_secret;
2323
static siphash_aligned_key_t ts_secret;
2424

25+
#define EPHEMERAL_PORT_SHUFFLE_PERIOD (10 * HZ)
26+
2527
static __always_inline void net_secret_init(void)
2628
{
2729
net_get_random_once(&net_secret, sizeof(net_secret));
@@ -94,17 +96,19 @@ u32 secure_tcpv6_seq(const __be32 *saddr, const __be32 *daddr,
9496
}
9597
EXPORT_SYMBOL(secure_tcpv6_seq);
9698

97-
u32 secure_ipv6_port_ephemeral(const __be32 *saddr, const __be32 *daddr,
99+
u64 secure_ipv6_port_ephemeral(const __be32 *saddr, const __be32 *daddr,
98100
__be16 dport)
99101
{
100102
const struct {
101103
struct in6_addr saddr;
102104
struct in6_addr daddr;
105+
unsigned int timeseed;
103106
__be16 dport;
104107
} __aligned(SIPHASH_ALIGNMENT) combined = {
105108
.saddr = *(struct in6_addr *)saddr,
106109
.daddr = *(struct in6_addr *)daddr,
107-
.dport = dport
110+
.timeseed = jiffies / EPHEMERAL_PORT_SHUFFLE_PERIOD,
111+
.dport = dport,
108112
};
109113
net_secret_init();
110114
return siphash(&combined, offsetofend(typeof(combined), dport),
@@ -142,11 +146,13 @@ u32 secure_tcp_seq(__be32 saddr, __be32 daddr,
142146
}
143147
EXPORT_SYMBOL_GPL(secure_tcp_seq);
144148

145-
u32 secure_ipv4_port_ephemeral(__be32 saddr, __be32 daddr, __be16 dport)
149+
u64 secure_ipv4_port_ephemeral(__be32 saddr, __be32 daddr, __be16 dport)
146150
{
147151
net_secret_init();
148-
return siphash_3u32((__force u32)saddr, (__force u32)daddr,
149-
(__force u16)dport, &net_secret);
152+
return siphash_4u32((__force u32)saddr, (__force u32)daddr,
153+
(__force u16)dport,
154+
jiffies / EPHEMERAL_PORT_SHUFFLE_PERIOD,
155+
&net_secret);
150156
}
151157
EXPORT_SYMBOL_GPL(secure_ipv4_port_ephemeral);
152158
#endif

net/ipv4/inet_hashtables.c

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,7 @@ static int __inet_check_established(struct inet_timewait_death_row *death_row,
504504
return -EADDRNOTAVAIL;
505505
}
506506

507-
static u32 inet_sk_port_offset(const struct sock *sk)
507+
static u64 inet_sk_port_offset(const struct sock *sk)
508508
{
509509
const struct inet_sock *inet = inet_sk(sk);
510510

@@ -726,15 +726,17 @@ EXPORT_SYMBOL_GPL(inet_unhash);
726726
* Note that we use 32bit integers (vs RFC 'short integers')
727727
* because 2^16 is not a multiple of num_ephemeral and this
728728
* property might be used by clever attacker.
729-
* RFC claims using TABLE_LENGTH=10 buckets gives an improvement,
730-
* we use 256 instead to really give more isolation and
731-
* privacy, this only consumes 1 KB of kernel memory.
729+
* RFC claims using TABLE_LENGTH=10 buckets gives an improvement, though
730+
* attacks were since demonstrated, thus we use 65536 instead to really
731+
* give more isolation and privacy, at the expense of 256kB of kernel
732+
* memory.
732733
*/
733-
#define INET_TABLE_PERTURB_SHIFT 8
734-
static u32 table_perturb[1 << INET_TABLE_PERTURB_SHIFT];
734+
#define INET_TABLE_PERTURB_SHIFT 16
735+
#define INET_TABLE_PERTURB_SIZE (1 << INET_TABLE_PERTURB_SHIFT)
736+
static u32 *table_perturb;
735737

736738
int __inet_hash_connect(struct inet_timewait_death_row *death_row,
737-
struct sock *sk, u32 port_offset,
739+
struct sock *sk, u64 port_offset,
738740
int (*check_established)(struct inet_timewait_death_row *,
739741
struct sock *, __u16, struct inet_timewait_sock **))
740742
{
@@ -774,10 +776,13 @@ int __inet_hash_connect(struct inet_timewait_death_row *death_row,
774776
if (likely(remaining > 1))
775777
remaining &= ~1U;
776778

777-
net_get_random_once(table_perturb, sizeof(table_perturb));
778-
index = hash_32(port_offset, INET_TABLE_PERTURB_SHIFT);
779+
net_get_random_once(table_perturb,
780+
INET_TABLE_PERTURB_SIZE * sizeof(*table_perturb));
781+
index = port_offset & (INET_TABLE_PERTURB_SIZE - 1);
782+
783+
offset = READ_ONCE(table_perturb[index]) + (port_offset >> 32);
784+
offset %= remaining;
779785

780-
offset = (READ_ONCE(table_perturb[index]) + port_offset) % remaining;
781786
/* In first pass we try ports of @low parity.
782787
* inet_csk_get_port() does the opposite choice.
783788
*/
@@ -831,11 +836,12 @@ int __inet_hash_connect(struct inet_timewait_death_row *death_row,
831836
return -EADDRNOTAVAIL;
832837

833838
ok:
834-
/* If our first attempt found a candidate, skip next candidate
835-
* in 1/16 of cases to add some noise.
839+
/* Here we want to add a little bit of randomness to the next source
840+
* port that will be chosen. We use a max() with a random here so that
841+
* on low contention the randomness is maximal and on high contention
842+
* it may be inexistent.
836843
*/
837-
if (!i && !(prandom_u32() % 16))
838-
i = 2;
844+
i = max_t(int, i, (prandom_u32() & 7) * 2);
839845
WRITE_ONCE(table_perturb[index], READ_ONCE(table_perturb[index]) + i + 2);
840846

841847
/* Head lock still held and bh's disabled */
@@ -859,7 +865,7 @@ int __inet_hash_connect(struct inet_timewait_death_row *death_row,
859865
int inet_hash_connect(struct inet_timewait_death_row *death_row,
860866
struct sock *sk)
861867
{
862-
u32 port_offset = 0;
868+
u64 port_offset = 0;
863869

864870
if (!inet_sk(sk)->inet_num)
865871
port_offset = inet_sk_port_offset(sk);
@@ -909,6 +915,12 @@ void __init inet_hashinfo2_init(struct inet_hashinfo *h, const char *name,
909915
low_limit,
910916
high_limit);
911917
init_hashinfo_lhash2(h);
918+
919+
/* this one is used for source ports of outgoing connections */
920+
table_perturb = kmalloc_array(INET_TABLE_PERTURB_SIZE,
921+
sizeof(*table_perturb), GFP_KERNEL);
922+
if (!table_perturb)
923+
panic("TCP: failed to alloc table_perturb");
912924
}
913925

914926
int inet_hashinfo2_init_mod(struct inet_hashinfo *h)

net/ipv6/inet6_hashtables.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ static int __inet6_check_established(struct inet_timewait_death_row *death_row,
308308
return -EADDRNOTAVAIL;
309309
}
310310

311-
static u32 inet6_sk_port_offset(const struct sock *sk)
311+
static u64 inet6_sk_port_offset(const struct sock *sk)
312312
{
313313
const struct inet_sock *inet = inet_sk(sk);
314314

@@ -320,7 +320,7 @@ static u32 inet6_sk_port_offset(const struct sock *sk)
320320
int inet6_hash_connect(struct inet_timewait_death_row *death_row,
321321
struct sock *sk)
322322
{
323-
u32 port_offset = 0;
323+
u64 port_offset = 0;
324324

325325
if (!inet_sk(sk)->inet_num)
326326
port_offset = inet6_sk_port_offset(sk);

0 commit comments

Comments
 (0)