diff options
author | Eric Dumazet <edumazet@google.com> | 2015-04-23 21:03:44 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2015-04-24 11:39:15 -0400 |
commit | b357a364c57c940ddb932224542494363df37378 (patch) | |
tree | 84657c956c5c64a2ae058b8285271f778b1aea67 /net | |
parent | 1d8dc3d3c8f1d8ee1da9d54c5d7c8694419ade42 (diff) |
inet: fix possible panic in reqsk_queue_unlink()
[ 3897.923145] BUG: unable to handle kernel NULL pointer dereference at
0000000000000080
[ 3897.931025] IP: [<ffffffffa9f27686>] reqsk_timer_handler+0x1a6/0x243
There is a race when reqsk_timer_handler() and tcp_check_req() call
inet_csk_reqsk_queue_unlink() on the same req at the same time.
Before commit fa76ce7328b2 ("inet: get rid of central tcp/dccp listener
timer"), listener spinlock was held and race could not happen.
To solve this bug, we change reqsk_queue_unlink() to not assume req
must be found, and we return a status, to conditionally release a
refcount on the request sock.
This also means tcp_check_req() in non fastopen case might or not
consume req refcount, so tcp_v6_hnd_req() & tcp_v4_hnd_req() have
to properly handle this.
(Same remark for dccp_check_req() and its callers)
inet_csk_reqsk_queue_drop() is now too big to be inlined, as it is
called 4 times in tcp and 3 times in dccp.
Fixes: fa76ce7328b2 ("inet: get rid of central tcp/dccp listener timer")
Signed-off-by: Eric Dumazet <edumazet@google.com>
Reported-by: Yuchung Cheng <ycheng@google.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net')
-rw-r--r-- | net/dccp/ipv4.c | 3 | ||||
-rw-r--r-- | net/dccp/ipv6.c | 3 | ||||
-rw-r--r-- | net/dccp/minisocks.c | 3 | ||||
-rw-r--r-- | net/ipv4/inet_connection_sock.c | 34 | ||||
-rw-r--r-- | net/ipv4/tcp_ipv4.c | 3 | ||||
-rw-r--r-- | net/ipv4/tcp_minisocks.c | 7 | ||||
-rw-r--r-- | net/ipv6/tcp_ipv6.c | 3 |
7 files changed, 47 insertions, 9 deletions
diff --git a/net/dccp/ipv4.c b/net/dccp/ipv4.c index 2b4f21d34df6..ccf4c5629b3c 100644 --- a/net/dccp/ipv4.c +++ b/net/dccp/ipv4.c | |||
@@ -453,7 +453,8 @@ static struct sock *dccp_v4_hnd_req(struct sock *sk, struct sk_buff *skb) | |||
453 | iph->saddr, iph->daddr); | 453 | iph->saddr, iph->daddr); |
454 | if (req) { | 454 | if (req) { |
455 | nsk = dccp_check_req(sk, skb, req); | 455 | nsk = dccp_check_req(sk, skb, req); |
456 | reqsk_put(req); | 456 | if (!nsk) |
457 | reqsk_put(req); | ||
457 | return nsk; | 458 | return nsk; |
458 | } | 459 | } |
459 | nsk = inet_lookup_established(sock_net(sk), &dccp_hashinfo, | 460 | nsk = inet_lookup_established(sock_net(sk), &dccp_hashinfo, |
diff --git a/net/dccp/ipv6.c b/net/dccp/ipv6.c index 9d0551092c6c..5165571f397a 100644 --- a/net/dccp/ipv6.c +++ b/net/dccp/ipv6.c | |||
@@ -301,7 +301,8 @@ static struct sock *dccp_v6_hnd_req(struct sock *sk,struct sk_buff *skb) | |||
301 | &iph->daddr, inet6_iif(skb)); | 301 | &iph->daddr, inet6_iif(skb)); |
302 | if (req) { | 302 | if (req) { |
303 | nsk = dccp_check_req(sk, skb, req); | 303 | nsk = dccp_check_req(sk, skb, req); |
304 | reqsk_put(req); | 304 | if (!nsk) |
305 | reqsk_put(req); | ||
305 | return nsk; | 306 | return nsk; |
306 | } | 307 | } |
307 | nsk = __inet6_lookup_established(sock_net(sk), &dccp_hashinfo, | 308 | nsk = __inet6_lookup_established(sock_net(sk), &dccp_hashinfo, |
diff --git a/net/dccp/minisocks.c b/net/dccp/minisocks.c index 5f566663e47f..30addee2dd03 100644 --- a/net/dccp/minisocks.c +++ b/net/dccp/minisocks.c | |||
@@ -186,8 +186,7 @@ struct sock *dccp_check_req(struct sock *sk, struct sk_buff *skb, | |||
186 | if (child == NULL) | 186 | if (child == NULL) |
187 | goto listen_overflow; | 187 | goto listen_overflow; |
188 | 188 | ||
189 | inet_csk_reqsk_queue_unlink(sk, req); | 189 | inet_csk_reqsk_queue_drop(sk, req); |
190 | inet_csk_reqsk_queue_removed(sk, req); | ||
191 | inet_csk_reqsk_queue_add(sk, req, child); | 190 | inet_csk_reqsk_queue_add(sk, req, child); |
192 | out: | 191 | out: |
193 | return child; | 192 | return child; |
diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c index 5c3dd6267ed3..8976ca423a07 100644 --- a/net/ipv4/inet_connection_sock.c +++ b/net/ipv4/inet_connection_sock.c | |||
@@ -564,6 +564,40 @@ int inet_rtx_syn_ack(struct sock *parent, struct request_sock *req) | |||
564 | } | 564 | } |
565 | EXPORT_SYMBOL(inet_rtx_syn_ack); | 565 | EXPORT_SYMBOL(inet_rtx_syn_ack); |
566 | 566 | ||
567 | /* return true if req was found in the syn_table[] */ | ||
568 | static bool reqsk_queue_unlink(struct request_sock_queue *queue, | ||
569 | struct request_sock *req) | ||
570 | { | ||
571 | struct listen_sock *lopt = queue->listen_opt; | ||
572 | struct request_sock **prev; | ||
573 | bool found = false; | ||
574 | |||
575 | spin_lock(&queue->syn_wait_lock); | ||
576 | |||
577 | for (prev = &lopt->syn_table[req->rsk_hash]; *prev != NULL; | ||
578 | prev = &(*prev)->dl_next) { | ||
579 | if (*prev == req) { | ||
580 | *prev = req->dl_next; | ||
581 | found = true; | ||
582 | break; | ||
583 | } | ||
584 | } | ||
585 | |||
586 | spin_unlock(&queue->syn_wait_lock); | ||
587 | if (del_timer(&req->rsk_timer)) | ||
588 | reqsk_put(req); | ||
589 | return found; | ||
590 | } | ||
591 | |||
592 | void inet_csk_reqsk_queue_drop(struct sock *sk, struct request_sock *req) | ||
593 | { | ||
594 | if (reqsk_queue_unlink(&inet_csk(sk)->icsk_accept_queue, req)) { | ||
595 | reqsk_queue_removed(&inet_csk(sk)->icsk_accept_queue, req); | ||
596 | reqsk_put(req); | ||
597 | } | ||
598 | } | ||
599 | EXPORT_SYMBOL(inet_csk_reqsk_queue_drop); | ||
600 | |||
567 | static void reqsk_timer_handler(unsigned long data) | 601 | static void reqsk_timer_handler(unsigned long data) |
568 | { | 602 | { |
569 | struct request_sock *req = (struct request_sock *)data; | 603 | struct request_sock *req = (struct request_sock *)data; |
diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c index 3571f2be4470..fc1c658ec6c1 100644 --- a/net/ipv4/tcp_ipv4.c +++ b/net/ipv4/tcp_ipv4.c | |||
@@ -1348,7 +1348,8 @@ static struct sock *tcp_v4_hnd_req(struct sock *sk, struct sk_buff *skb) | |||
1348 | req = inet_csk_search_req(sk, th->source, iph->saddr, iph->daddr); | 1348 | req = inet_csk_search_req(sk, th->source, iph->saddr, iph->daddr); |
1349 | if (req) { | 1349 | if (req) { |
1350 | nsk = tcp_check_req(sk, skb, req, false); | 1350 | nsk = tcp_check_req(sk, skb, req, false); |
1351 | reqsk_put(req); | 1351 | if (!nsk) |
1352 | reqsk_put(req); | ||
1352 | return nsk; | 1353 | return nsk; |
1353 | } | 1354 | } |
1354 | 1355 | ||
diff --git a/net/ipv4/tcp_minisocks.c b/net/ipv4/tcp_minisocks.c index 63d6311b5365..e5d7649136fc 100644 --- a/net/ipv4/tcp_minisocks.c +++ b/net/ipv4/tcp_minisocks.c | |||
@@ -755,10 +755,11 @@ struct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb, | |||
755 | if (!child) | 755 | if (!child) |
756 | goto listen_overflow; | 756 | goto listen_overflow; |
757 | 757 | ||
758 | inet_csk_reqsk_queue_unlink(sk, req); | 758 | inet_csk_reqsk_queue_drop(sk, req); |
759 | inet_csk_reqsk_queue_removed(sk, req); | ||
760 | |||
761 | inet_csk_reqsk_queue_add(sk, req, child); | 759 | inet_csk_reqsk_queue_add(sk, req, child); |
760 | /* Warning: caller must not call reqsk_put(req); | ||
761 | * child stole last reference on it. | ||
762 | */ | ||
762 | return child; | 763 | return child; |
763 | 764 | ||
764 | listen_overflow: | 765 | listen_overflow: |
diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c index ad51df85aa00..b6575d665568 100644 --- a/net/ipv6/tcp_ipv6.c +++ b/net/ipv6/tcp_ipv6.c | |||
@@ -946,7 +946,8 @@ static struct sock *tcp_v6_hnd_req(struct sock *sk, struct sk_buff *skb) | |||
946 | &ipv6_hdr(skb)->daddr, tcp_v6_iif(skb)); | 946 | &ipv6_hdr(skb)->daddr, tcp_v6_iif(skb)); |
947 | if (req) { | 947 | if (req) { |
948 | nsk = tcp_check_req(sk, skb, req, false); | 948 | nsk = tcp_check_req(sk, skb, req, false); |
949 | reqsk_put(req); | 949 | if (!nsk) |
950 | reqsk_put(req); | ||
950 | return nsk; | 951 | return nsk; |
951 | } | 952 | } |
952 | nsk = __inet6_lookup_established(sock_net(sk), &tcp_hashinfo, | 953 | nsk = __inet6_lookup_established(sock_net(sk), &tcp_hashinfo, |