Skip to content

Commit 8c5dae4

Browse files
q2venkuba-moo
authored andcommitted
dccp/tcp: Update saddr under bhash's lock.
When we call connect() for a socket bound to a wildcard address, we update saddr locklessly. However, it could result in a data race; another thread iterating over bhash might see a corrupted address. Let's update saddr under the bhash bucket's lock. Fixes: 3df80d9 ("[DCCP]: Introduce DCCPv6") Fixes: 7c65787 ("[DCCP]: Initial implementation") Fixes: 1da177e ("Linux-2.6.12-rc2") Signed-off-by: Kuniyuki Iwashima <[email protected]> Acked-by: Joanne Koong <[email protected]> Reviewed-by: Eric Dumazet <[email protected]> Signed-off-by: Jakub Kicinski <[email protected]>
1 parent 8acdad3 commit 8c5dae4

File tree

7 files changed

+56
-86
lines changed

7 files changed

+56
-86
lines changed

include/net/inet_hashtables.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ inet_bhash2_addr_any_hashbucket(const struct sock *sk, const struct net *net, in
281281
* sk_v6_rcv_saddr (ipv6) changes after it has been binded. The socket's
282282
* rcv_saddr field should already have been updated when this is called.
283283
*/
284-
int inet_bhash2_update_saddr(struct inet_bind_hashbucket *prev_saddr, struct sock *sk);
284+
int inet_bhash2_update_saddr(struct sock *sk, void *saddr, int family);
285285

286286
void inet_bind_hash(struct sock *sk, struct inet_bind_bucket *tb,
287287
struct inet_bind2_bucket *tb2, unsigned short port);

net/dccp/ipv4.c

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,10 @@ static unsigned int dccp_v4_pernet_id __read_mostly;
4545
int dccp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
4646
{
4747
const struct sockaddr_in *usin = (struct sockaddr_in *)uaddr;
48-
struct inet_bind_hashbucket *prev_addr_hashbucket = NULL;
49-
__be32 daddr, nexthop, prev_sk_rcv_saddr;
5048
struct inet_sock *inet = inet_sk(sk);
5149
struct dccp_sock *dp = dccp_sk(sk);
5250
__be16 orig_sport, orig_dport;
51+
__be32 daddr, nexthop;
5352
struct flowi4 *fl4;
5453
struct rtable *rt;
5554
int err;
@@ -91,26 +90,13 @@ int dccp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
9190
daddr = fl4->daddr;
9291

9392
if (inet->inet_saddr == 0) {
94-
if (inet_csk(sk)->icsk_bind2_hash) {
95-
prev_addr_hashbucket =
96-
inet_bhashfn_portaddr(&dccp_hashinfo, sk,
97-
sock_net(sk),
98-
inet->inet_num);
99-
prev_sk_rcv_saddr = sk->sk_rcv_saddr;
100-
}
101-
inet->inet_saddr = fl4->saddr;
102-
}
103-
104-
sk_rcv_saddr_set(sk, inet->inet_saddr);
105-
106-
if (prev_addr_hashbucket) {
107-
err = inet_bhash2_update_saddr(prev_addr_hashbucket, sk);
93+
err = inet_bhash2_update_saddr(sk, &fl4->saddr, AF_INET);
10894
if (err) {
109-
inet->inet_saddr = 0;
110-
sk_rcv_saddr_set(sk, prev_sk_rcv_saddr);
11195
ip_rt_put(rt);
11296
return err;
11397
}
98+
} else {
99+
sk_rcv_saddr_set(sk, inet->inet_saddr);
114100
}
115101

116102
inet->inet_dport = usin->sin_port;

net/dccp/ipv6.c

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -934,26 +934,11 @@ static int dccp_v6_connect(struct sock *sk, struct sockaddr *uaddr,
934934
}
935935

936936
if (saddr == NULL) {
937-
struct inet_bind_hashbucket *prev_addr_hashbucket = NULL;
938-
struct in6_addr prev_v6_rcv_saddr;
939-
940-
if (icsk->icsk_bind2_hash) {
941-
prev_addr_hashbucket = inet_bhashfn_portaddr(&dccp_hashinfo,
942-
sk, sock_net(sk),
943-
inet->inet_num);
944-
prev_v6_rcv_saddr = sk->sk_v6_rcv_saddr;
945-
}
946-
947937
saddr = &fl6.saddr;
948-
sk->sk_v6_rcv_saddr = *saddr;
949-
950-
if (prev_addr_hashbucket) {
951-
err = inet_bhash2_update_saddr(prev_addr_hashbucket, sk);
952-
if (err) {
953-
sk->sk_v6_rcv_saddr = prev_v6_rcv_saddr;
954-
goto failure;
955-
}
956-
}
938+
939+
err = inet_bhash2_update_saddr(sk, saddr, AF_INET6);
940+
if (err)
941+
goto failure;
957942
}
958943

959944
/* set the source address */

net/ipv4/af_inet.c

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1230,7 +1230,6 @@ EXPORT_SYMBOL(inet_unregister_protosw);
12301230

12311231
static int inet_sk_reselect_saddr(struct sock *sk)
12321232
{
1233-
struct inet_bind_hashbucket *prev_addr_hashbucket;
12341233
struct inet_sock *inet = inet_sk(sk);
12351234
__be32 old_saddr = inet->inet_saddr;
12361235
__be32 daddr = inet->inet_daddr;
@@ -1260,16 +1259,8 @@ static int inet_sk_reselect_saddr(struct sock *sk)
12601259
return 0;
12611260
}
12621261

1263-
prev_addr_hashbucket =
1264-
inet_bhashfn_portaddr(tcp_or_dccp_get_hashinfo(sk), sk,
1265-
sock_net(sk), inet->inet_num);
1266-
1267-
inet->inet_saddr = inet->inet_rcv_saddr = new_saddr;
1268-
1269-
err = inet_bhash2_update_saddr(prev_addr_hashbucket, sk);
1262+
err = inet_bhash2_update_saddr(sk, &new_saddr, AF_INET);
12701263
if (err) {
1271-
inet->inet_saddr = old_saddr;
1272-
inet->inet_rcv_saddr = old_saddr;
12731264
ip_rt_put(rt);
12741265
return err;
12751266
}

net/ipv4/inet_hashtables.c

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -858,14 +858,34 @@ inet_bhash2_addr_any_hashbucket(const struct sock *sk, const struct net *net, in
858858
return &hinfo->bhash2[hash & (hinfo->bhash_size - 1)];
859859
}
860860

861-
int inet_bhash2_update_saddr(struct inet_bind_hashbucket *prev_saddr, struct sock *sk)
861+
static void inet_update_saddr(struct sock *sk, void *saddr, int family)
862+
{
863+
if (family == AF_INET) {
864+
inet_sk(sk)->inet_saddr = *(__be32 *)saddr;
865+
sk_rcv_saddr_set(sk, inet_sk(sk)->inet_saddr);
866+
}
867+
#if IS_ENABLED(CONFIG_IPV6)
868+
else {
869+
sk->sk_v6_rcv_saddr = *(struct in6_addr *)saddr;
870+
}
871+
#endif
872+
}
873+
874+
int inet_bhash2_update_saddr(struct sock *sk, void *saddr, int family)
862875
{
863876
struct inet_hashinfo *hinfo = tcp_or_dccp_get_hashinfo(sk);
877+
struct inet_bind_hashbucket *head, *head2;
864878
struct inet_bind2_bucket *tb2, *new_tb2;
865879
int l3mdev = inet_sk_bound_l3mdev(sk);
866-
struct inet_bind_hashbucket *head2;
867880
int port = inet_sk(sk)->inet_num;
868881
struct net *net = sock_net(sk);
882+
int bhash;
883+
884+
if (!inet_csk(sk)->icsk_bind2_hash) {
885+
/* Not bind()ed before. */
886+
inet_update_saddr(sk, saddr, family);
887+
return 0;
888+
}
869889

870890
/* Allocate a bind2 bucket ahead of time to avoid permanently putting
871891
* the bhash2 table in an inconsistent state if a new tb2 bucket
@@ -875,22 +895,35 @@ int inet_bhash2_update_saddr(struct inet_bind_hashbucket *prev_saddr, struct soc
875895
if (!new_tb2)
876896
return -ENOMEM;
877897

898+
bhash = inet_bhashfn(net, port, hinfo->bhash_size);
899+
head = &hinfo->bhash[bhash];
878900
head2 = inet_bhashfn_portaddr(hinfo, sk, net, port);
879901

880-
spin_lock_bh(&prev_saddr->lock);
902+
/* If we change saddr locklessly, another thread
903+
* iterating over bhash might see corrupted address.
904+
*/
905+
spin_lock_bh(&head->lock);
906+
907+
spin_lock(&head2->lock);
881908
__sk_del_bind2_node(sk);
882909
inet_bind2_bucket_destroy(hinfo->bind2_bucket_cachep, inet_csk(sk)->icsk_bind2_hash);
883-
spin_unlock_bh(&prev_saddr->lock);
910+
spin_unlock(&head2->lock);
911+
912+
inet_update_saddr(sk, saddr, family);
884913

885-
spin_lock_bh(&head2->lock);
914+
head2 = inet_bhashfn_portaddr(hinfo, sk, net, port);
915+
916+
spin_lock(&head2->lock);
886917
tb2 = inet_bind2_bucket_find(head2, net, port, l3mdev, sk);
887918
if (!tb2) {
888919
tb2 = new_tb2;
889920
inet_bind2_bucket_init(tb2, net, head2, port, l3mdev, sk);
890921
}
891922
sk_add_bind2_node(sk, &tb2->owners);
892923
inet_csk(sk)->icsk_bind2_hash = tb2;
893-
spin_unlock_bh(&head2->lock);
924+
spin_unlock(&head2->lock);
925+
926+
spin_unlock_bh(&head->lock);
894927

895928
if (tb2 != new_tb2)
896929
kmem_cache_free(hinfo->bind2_bucket_cachep, new_tb2);

net/ipv4/tcp_ipv4.c

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -199,15 +199,14 @@ static int tcp_v4_pre_connect(struct sock *sk, struct sockaddr *uaddr,
199199
/* This will initiate an outgoing connection. */
200200
int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
201201
{
202-
struct inet_bind_hashbucket *prev_addr_hashbucket = NULL;
203202
struct sockaddr_in *usin = (struct sockaddr_in *)uaddr;
204203
struct inet_timewait_death_row *tcp_death_row;
205-
__be32 daddr, nexthop, prev_sk_rcv_saddr;
206204
struct inet_sock *inet = inet_sk(sk);
207205
struct tcp_sock *tp = tcp_sk(sk);
208206
struct ip_options_rcu *inet_opt;
209207
struct net *net = sock_net(sk);
210208
__be16 orig_sport, orig_dport;
209+
__be32 daddr, nexthop;
211210
struct flowi4 *fl4;
212211
struct rtable *rt;
213212
int err;
@@ -251,24 +250,13 @@ int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
251250
tcp_death_row = &sock_net(sk)->ipv4.tcp_death_row;
252251

253252
if (!inet->inet_saddr) {
254-
if (inet_csk(sk)->icsk_bind2_hash) {
255-
prev_addr_hashbucket = inet_bhashfn_portaddr(tcp_death_row->hashinfo,
256-
sk, net, inet->inet_num);
257-
prev_sk_rcv_saddr = sk->sk_rcv_saddr;
258-
}
259-
inet->inet_saddr = fl4->saddr;
260-
}
261-
262-
sk_rcv_saddr_set(sk, inet->inet_saddr);
263-
264-
if (prev_addr_hashbucket) {
265-
err = inet_bhash2_update_saddr(prev_addr_hashbucket, sk);
253+
err = inet_bhash2_update_saddr(sk, &fl4->saddr, AF_INET);
266254
if (err) {
267-
inet->inet_saddr = 0;
268-
sk_rcv_saddr_set(sk, prev_sk_rcv_saddr);
269255
ip_rt_put(rt);
270256
return err;
271257
}
258+
} else {
259+
sk_rcv_saddr_set(sk, inet->inet_saddr);
272260
}
273261

274262
if (tp->rx_opt.ts_recent_stamp && inet->inet_daddr != daddr) {

net/ipv6/tcp_ipv6.c

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -292,24 +292,11 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr *uaddr,
292292
tcp_death_row = &sock_net(sk)->ipv4.tcp_death_row;
293293

294294
if (!saddr) {
295-
struct inet_bind_hashbucket *prev_addr_hashbucket = NULL;
296-
struct in6_addr prev_v6_rcv_saddr;
297-
298-
if (icsk->icsk_bind2_hash) {
299-
prev_addr_hashbucket = inet_bhashfn_portaddr(tcp_death_row->hashinfo,
300-
sk, net, inet->inet_num);
301-
prev_v6_rcv_saddr = sk->sk_v6_rcv_saddr;
302-
}
303295
saddr = &fl6.saddr;
304-
sk->sk_v6_rcv_saddr = *saddr;
305296

306-
if (prev_addr_hashbucket) {
307-
err = inet_bhash2_update_saddr(prev_addr_hashbucket, sk);
308-
if (err) {
309-
sk->sk_v6_rcv_saddr = prev_v6_rcv_saddr;
310-
goto failure;
311-
}
312-
}
297+
err = inet_bhash2_update_saddr(sk, saddr, AF_INET6);
298+
if (err)
299+
goto failure;
313300
}
314301

315302
/* set the source address */

0 commit comments

Comments
 (0)