diff options
author | Eric Dumazet <eric.dumazet@gmail.com> | 2009-12-03 22:46:54 -0500 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2009-12-08 23:17:51 -0500 |
commit | 9327f7053e3993c125944fdb137a0618319ef2a0 (patch) | |
tree | 3cee7de049a2468bef930b1832c42bd1b2e69e9a /net/ipv4 | |
parent | 74757d49016a8b06ca028196886641d7aeb78de5 (diff) |
tcp: Fix a connect() race with timewait sockets
First patch changes __inet_hash_nolisten() and __inet6_hash()
to get a timewait parameter to be able to unhash it from ehash
at same time the new socket is inserted in hash.
This makes sure timewait socket wont be found by a concurrent
writer in __inet_check_established()
Reported-by: kapil dakhane <kdakhane@gmail.com>
Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/ipv4')
-rw-r--r-- | net/ipv4/inet_hashtables.c | 22 | ||||
-rw-r--r-- | net/ipv4/tcp_ipv4.c | 2 |
2 files changed, 17 insertions, 7 deletions
diff --git a/net/ipv4/inet_hashtables.c b/net/ipv4/inet_hashtables.c index 21e5e32d8c60..c4201b7ece38 100644 --- a/net/ipv4/inet_hashtables.c +++ b/net/ipv4/inet_hashtables.c | |||
@@ -351,12 +351,13 @@ static inline u32 inet_sk_port_offset(const struct sock *sk) | |||
351 | inet->inet_dport); | 351 | inet->inet_dport); |
352 | } | 352 | } |
353 | 353 | ||
354 | void __inet_hash_nolisten(struct sock *sk) | 354 | int __inet_hash_nolisten(struct sock *sk, struct inet_timewait_sock *tw) |
355 | { | 355 | { |
356 | struct inet_hashinfo *hashinfo = sk->sk_prot->h.hashinfo; | 356 | struct inet_hashinfo *hashinfo = sk->sk_prot->h.hashinfo; |
357 | struct hlist_nulls_head *list; | 357 | struct hlist_nulls_head *list; |
358 | spinlock_t *lock; | 358 | spinlock_t *lock; |
359 | struct inet_ehash_bucket *head; | 359 | struct inet_ehash_bucket *head; |
360 | int twrefcnt = 0; | ||
360 | 361 | ||
361 | WARN_ON(!sk_unhashed(sk)); | 362 | WARN_ON(!sk_unhashed(sk)); |
362 | 363 | ||
@@ -367,8 +368,13 @@ void __inet_hash_nolisten(struct sock *sk) | |||
367 | 368 | ||
368 | spin_lock(lock); | 369 | spin_lock(lock); |
369 | __sk_nulls_add_node_rcu(sk, list); | 370 | __sk_nulls_add_node_rcu(sk, list); |
371 | if (tw) { | ||
372 | WARN_ON(sk->sk_hash != tw->tw_hash); | ||
373 | twrefcnt = inet_twsk_unhash(tw); | ||
374 | } | ||
370 | spin_unlock(lock); | 375 | spin_unlock(lock); |
371 | sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1); | 376 | sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1); |
377 | return twrefcnt; | ||
372 | } | 378 | } |
373 | EXPORT_SYMBOL_GPL(__inet_hash_nolisten); | 379 | EXPORT_SYMBOL_GPL(__inet_hash_nolisten); |
374 | 380 | ||
@@ -378,7 +384,7 @@ static void __inet_hash(struct sock *sk) | |||
378 | struct inet_listen_hashbucket *ilb; | 384 | struct inet_listen_hashbucket *ilb; |
379 | 385 | ||
380 | if (sk->sk_state != TCP_LISTEN) { | 386 | if (sk->sk_state != TCP_LISTEN) { |
381 | __inet_hash_nolisten(sk); | 387 | __inet_hash_nolisten(sk, NULL); |
382 | return; | 388 | return; |
383 | } | 389 | } |
384 | 390 | ||
@@ -427,7 +433,7 @@ int __inet_hash_connect(struct inet_timewait_death_row *death_row, | |||
427 | struct sock *sk, u32 port_offset, | 433 | struct sock *sk, u32 port_offset, |
428 | int (*check_established)(struct inet_timewait_death_row *, | 434 | int (*check_established)(struct inet_timewait_death_row *, |
429 | struct sock *, __u16, struct inet_timewait_sock **), | 435 | struct sock *, __u16, struct inet_timewait_sock **), |
430 | void (*hash)(struct sock *sk)) | 436 | int (*hash)(struct sock *sk, struct inet_timewait_sock *twp)) |
431 | { | 437 | { |
432 | struct inet_hashinfo *hinfo = death_row->hashinfo; | 438 | struct inet_hashinfo *hinfo = death_row->hashinfo; |
433 | const unsigned short snum = inet_sk(sk)->inet_num; | 439 | const unsigned short snum = inet_sk(sk)->inet_num; |
@@ -435,6 +441,7 @@ int __inet_hash_connect(struct inet_timewait_death_row *death_row, | |||
435 | struct inet_bind_bucket *tb; | 441 | struct inet_bind_bucket *tb; |
436 | int ret; | 442 | int ret; |
437 | struct net *net = sock_net(sk); | 443 | struct net *net = sock_net(sk); |
444 | int twrefcnt = 1; | ||
438 | 445 | ||
439 | if (!snum) { | 446 | if (!snum) { |
440 | int i, remaining, low, high, port; | 447 | int i, remaining, low, high, port; |
@@ -493,13 +500,16 @@ ok: | |||
493 | inet_bind_hash(sk, tb, port); | 500 | inet_bind_hash(sk, tb, port); |
494 | if (sk_unhashed(sk)) { | 501 | if (sk_unhashed(sk)) { |
495 | inet_sk(sk)->inet_sport = htons(port); | 502 | inet_sk(sk)->inet_sport = htons(port); |
496 | hash(sk); | 503 | twrefcnt += hash(sk, tw); |
497 | } | 504 | } |
498 | spin_unlock(&head->lock); | 505 | spin_unlock(&head->lock); |
499 | 506 | ||
500 | if (tw) { | 507 | if (tw) { |
501 | inet_twsk_deschedule(tw, death_row); | 508 | inet_twsk_deschedule(tw, death_row); |
502 | inet_twsk_put(tw); | 509 | while (twrefcnt) { |
510 | twrefcnt--; | ||
511 | inet_twsk_put(tw); | ||
512 | } | ||
503 | } | 513 | } |
504 | 514 | ||
505 | ret = 0; | 515 | ret = 0; |
@@ -510,7 +520,7 @@ ok: | |||
510 | tb = inet_csk(sk)->icsk_bind_hash; | 520 | tb = inet_csk(sk)->icsk_bind_hash; |
511 | spin_lock_bh(&head->lock); | 521 | spin_lock_bh(&head->lock); |
512 | if (sk_head(&tb->owners) == sk && !sk->sk_bind_node.next) { | 522 | if (sk_head(&tb->owners) == sk && !sk->sk_bind_node.next) { |
513 | hash(sk); | 523 | hash(sk, NULL); |
514 | spin_unlock_bh(&head->lock); | 524 | spin_unlock_bh(&head->lock); |
515 | return 0; | 525 | return 0; |
516 | } else { | 526 | } else { |
diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c index 29002ab26e0d..15e96030ce47 100644 --- a/net/ipv4/tcp_ipv4.c +++ b/net/ipv4/tcp_ipv4.c | |||
@@ -1464,7 +1464,7 @@ struct sock *tcp_v4_syn_recv_sock(struct sock *sk, struct sk_buff *skb, | |||
1464 | } | 1464 | } |
1465 | #endif | 1465 | #endif |
1466 | 1466 | ||
1467 | __inet_hash_nolisten(newsk); | 1467 | __inet_hash_nolisten(newsk, NULL); |
1468 | __inet_inherit_port(sk, newsk); | 1468 | __inet_inherit_port(sk, newsk); |
1469 | 1469 | ||
1470 | return newsk; | 1470 | return newsk; |