Commit 3cdaedae authored by Eric Dumazet's avatar Eric Dumazet Committed by David S. Miller

tcp: Fix a connect() race with timewait sockets

When we find a timewait connection in __inet_hash_connect() and reuse
it for a new connection request, we have a race window, releasing bind
list lock and reacquiring it in __inet_twsk_kill() to remove timewait
socket from list.

Another thread might find the timewait socket we already chose, leading to
list corruption and crashes.

Fix is to remove timewait socket from bind list before releasing the bind lock.

Note: This problem happens if sysctl_tcp_tw_reuse is set.
Reported-by: default avatarkapil dakhane <kdakhane@gmail.com>
Signed-off-by: default avatarEric Dumazet <eric.dumazet@gmail.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 9327f705
...@@ -201,6 +201,9 @@ extern void inet_twsk_put(struct inet_timewait_sock *tw); ...@@ -201,6 +201,9 @@ extern void inet_twsk_put(struct inet_timewait_sock *tw);
extern int inet_twsk_unhash(struct inet_timewait_sock *tw); extern int inet_twsk_unhash(struct inet_timewait_sock *tw);
extern int inet_twsk_bind_unhash(struct inet_timewait_sock *tw,
struct inet_hashinfo *hashinfo);
extern struct inet_timewait_sock *inet_twsk_alloc(const struct sock *sk, extern struct inet_timewait_sock *inet_twsk_alloc(const struct sock *sk,
const int state); const int state);
......
...@@ -502,6 +502,8 @@ ok: ...@@ -502,6 +502,8 @@ ok:
inet_sk(sk)->inet_sport = htons(port); inet_sk(sk)->inet_sport = htons(port);
twrefcnt += hash(sk, tw); twrefcnt += hash(sk, tw);
} }
if (tw)
twrefcnt += inet_twsk_bind_unhash(tw, hinfo);
spin_unlock(&head->lock); spin_unlock(&head->lock);
if (tw) { if (tw) {
......
...@@ -29,12 +29,29 @@ int inet_twsk_unhash(struct inet_timewait_sock *tw) ...@@ -29,12 +29,29 @@ int inet_twsk_unhash(struct inet_timewait_sock *tw)
return 1; return 1;
} }
/*
* unhash a timewait socket from bind hash
* lock must be hold by caller
*/
int inet_twsk_bind_unhash(struct inet_timewait_sock *tw,
struct inet_hashinfo *hashinfo)
{
struct inet_bind_bucket *tb = tw->tw_tb;
if (!tb)
return 0;
__hlist_del(&tw->tw_bind_node);
tw->tw_tb = NULL;
inet_bind_bucket_destroy(hashinfo->bind_bucket_cachep, tb);
return 1;
}
/* Must be called with locally disabled BHs. */ /* Must be called with locally disabled BHs. */
static void __inet_twsk_kill(struct inet_timewait_sock *tw, static void __inet_twsk_kill(struct inet_timewait_sock *tw,
struct inet_hashinfo *hashinfo) struct inet_hashinfo *hashinfo)
{ {
struct inet_bind_hashbucket *bhead; struct inet_bind_hashbucket *bhead;
struct inet_bind_bucket *tb;
int refcnt; int refcnt;
/* Unlink from established hashes. */ /* Unlink from established hashes. */
spinlock_t *lock = inet_ehash_lockp(hashinfo, tw->tw_hash); spinlock_t *lock = inet_ehash_lockp(hashinfo, tw->tw_hash);
...@@ -46,15 +63,11 @@ static void __inet_twsk_kill(struct inet_timewait_sock *tw, ...@@ -46,15 +63,11 @@ static void __inet_twsk_kill(struct inet_timewait_sock *tw,
/* Disassociate with bind bucket. */ /* Disassociate with bind bucket. */
bhead = &hashinfo->bhash[inet_bhashfn(twsk_net(tw), tw->tw_num, bhead = &hashinfo->bhash[inet_bhashfn(twsk_net(tw), tw->tw_num,
hashinfo->bhash_size)]; hashinfo->bhash_size)];
spin_lock(&bhead->lock); spin_lock(&bhead->lock);
tb = tw->tw_tb; refcnt += inet_twsk_bind_unhash(tw, hashinfo);
if (tb) {
__hlist_del(&tw->tw_bind_node);
tw->tw_tb = NULL;
inet_bind_bucket_destroy(hashinfo->bind_bucket_cachep, tb);
refcnt++;
}
spin_unlock(&bhead->lock); spin_unlock(&bhead->lock);
#ifdef SOCK_REFCNT_DEBUG #ifdef SOCK_REFCNT_DEBUG
if (atomic_read(&tw->tw_refcnt) != 1) { if (atomic_read(&tw->tw_refcnt) != 1) {
printk(KERN_DEBUG "%s timewait_sock %p refcnt=%d\n", printk(KERN_DEBUG "%s timewait_sock %p refcnt=%d\n",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment