Skip to content

Commit 69421bf

Browse files
q2venPaolo Abeni
authored andcommitted
udp: Update reuse->has_conns under reuseport_lock.
When we call connect() for a UDP socket in a reuseport group, we have to update sk->sk_reuseport_cb->has_conns to 1. Otherwise, the kernel could select a unconnected socket wrongly for packets sent to the connected socket. However, the current way to set has_conns is illegal and possible to trigger that problem. reuseport_has_conns() changes has_conns under rcu_read_lock(), which upgrades the RCU reader to the updater. Then, it must do the update under the updater's lock, reuseport_lock, but it doesn't for now. For this reason, there is a race below where we fail to set has_conns resulting in the wrong socket selection. To avoid the race, let's split the reader and updater with proper locking. cpu1 cpu2 +----+ +----+ __ip[46]_datagram_connect() reuseport_grow() . . |- reuseport_has_conns(sk, true) |- more_reuse = __reuseport_alloc(more_socks_size) | . | | |- rcu_read_lock() | |- reuse = rcu_dereference(sk->sk_reuseport_cb) | | | | | /* reuse->has_conns == 0 here */ | | |- more_reuse->has_conns = reuse->has_conns | |- reuse->has_conns = 1 | /* more_reuse->has_conns SHOULD BE 1 HERE */ | | | | | |- rcu_assign_pointer(reuse->socks[i]->sk_reuseport_cb, | | | more_reuse) | `- rcu_read_unlock() `- kfree_rcu(reuse, rcu) | |- sk->sk_state = TCP_ESTABLISHED Note the likely(reuse) in reuseport_has_conns_set() is always true, but we put the test there for ease of review. [0] For the record, usually, sk_reuseport_cb is changed under lock_sock(). The only exception is reuseport_grow() & TCP reqsk migration case. 1) shutdown() TCP listener, which is moved into the latter part of reuse->socks[] to migrate reqsk. 2) New listen() overflows reuse->socks[] and call reuseport_grow(). 3) reuse->max_socks overflows u16 with the new listener. 4) reuseport_grow() pops the old shutdown()ed listener from the array and update its sk->sk_reuseport_cb as NULL without lock_sock(). shutdown()ed TCP sk->sk_reuseport_cb can be changed without lock_sock(), but, reuseport_has_conns_set() is called only for UDP under lock_sock(), so likely(reuse) never be false in reuseport_has_conns_set(). [0]: https://lore.kernel.org/netdev/CANn89iLja=eQHbsM_Ta2sQF0tOGU8vAGrh_izRuuHjuO1ouUag@mail.gmail.com/ Fixes: acdcecc ("udp: correct reuseport selection with connected sockets") Signed-off-by: Kuniyuki Iwashima <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Paolo Abeni <[email protected]>
1 parent 402fe7a commit 69421bf

File tree

6 files changed

+25
-10
lines changed

6 files changed

+25
-10
lines changed

include/net/sock_reuseport.h

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,21 +43,20 @@ struct sock *reuseport_migrate_sock(struct sock *sk,
4343
extern int reuseport_attach_prog(struct sock *sk, struct bpf_prog *prog);
4444
extern int reuseport_detach_prog(struct sock *sk);
4545

46-
static inline bool reuseport_has_conns(struct sock *sk, bool set)
46+
static inline bool reuseport_has_conns(struct sock *sk)
4747
{
4848
struct sock_reuseport *reuse;
4949
bool ret = false;
5050

5151
rcu_read_lock();
5252
reuse = rcu_dereference(sk->sk_reuseport_cb);
53-
if (reuse) {
54-
if (set)
55-
reuse->has_conns = 1;
56-
ret = reuse->has_conns;
57-
}
53+
if (reuse && reuse->has_conns)
54+
ret = true;
5855
rcu_read_unlock();
5956

6057
return ret;
6158
}
6259

60+
void reuseport_has_conns_set(struct sock *sk);
61+
6362
#endif /* _SOCK_REUSEPORT_H */

net/core/sock_reuseport.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,22 @@ static DEFINE_IDA(reuseport_ida);
2121
static int reuseport_resurrect(struct sock *sk, struct sock_reuseport *old_reuse,
2222
struct sock_reuseport *reuse, bool bind_inany);
2323

24+
void reuseport_has_conns_set(struct sock *sk)
25+
{
26+
struct sock_reuseport *reuse;
27+
28+
if (!rcu_access_pointer(sk->sk_reuseport_cb))
29+
return;
30+
31+
spin_lock_bh(&reuseport_lock);
32+
reuse = rcu_dereference_protected(sk->sk_reuseport_cb,
33+
lockdep_is_held(&reuseport_lock));
34+
if (likely(reuse))
35+
reuse->has_conns = 1;
36+
spin_unlock_bh(&reuseport_lock);
37+
}
38+
EXPORT_SYMBOL(reuseport_has_conns_set);
39+
2440
static int reuseport_sock_index(struct sock *sk,
2541
const struct sock_reuseport *reuse,
2642
bool closed)

net/ipv4/datagram.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ int __ip4_datagram_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len
7070
}
7171
inet->inet_daddr = fl4->daddr;
7272
inet->inet_dport = usin->sin_port;
73-
reuseport_has_conns(sk, true);
73+
reuseport_has_conns_set(sk);
7474
sk->sk_state = TCP_ESTABLISHED;
7575
sk_set_txhash(sk);
7676
inet->inet_id = prandom_u32();

net/ipv4/udp.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ static struct sock *udp4_lib_lookup2(struct net *net,
448448
result = lookup_reuseport(net, sk, skb,
449449
saddr, sport, daddr, hnum);
450450
/* Fall back to scoring if group has connections */
451-
if (result && !reuseport_has_conns(sk, false))
451+
if (result && !reuseport_has_conns(sk))
452452
return result;
453453

454454
result = result ? : sk;

net/ipv6/datagram.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ int __ip6_datagram_connect(struct sock *sk, struct sockaddr *uaddr,
256256
goto out;
257257
}
258258

259-
reuseport_has_conns(sk, true);
259+
reuseport_has_conns_set(sk);
260260
sk->sk_state = TCP_ESTABLISHED;
261261
sk_set_txhash(sk);
262262
out:

net/ipv6/udp.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ static struct sock *udp6_lib_lookup2(struct net *net,
195195
result = lookup_reuseport(net, sk, skb,
196196
saddr, sport, daddr, hnum);
197197
/* Fall back to scoring if group has connections */
198-
if (result && !reuseport_has_conns(sk, false))
198+
if (result && !reuseport_has_conns(sk))
199199
return result;
200200

201201
result = result ? : sk;

0 commit comments

Comments
 (0)