| From c89dffc70b340780e5b933832d8c3e045ef3791e Mon Sep 17 00:00:00 2001 |
| From: Kuniyuki Iwashima <kuniyu@amazon.co.jp> |
| Date: Mon, 18 Jan 2021 14:59:20 +0900 |
| Subject: tcp: Fix potential use-after-free due to double kfree() |
| |
| From: Kuniyuki Iwashima <kuniyu@amazon.co.jp> |
| |
| commit c89dffc70b340780e5b933832d8c3e045ef3791e upstream. |
| |
| Receiving ACK with a valid SYN cookie, cookie_v4_check() allocates struct |
| request_sock and then can allocate inet_rsk(req)->ireq_opt. After that, |
| tcp_v4_syn_recv_sock() allocates struct sock and copies ireq_opt to |
| inet_sk(sk)->inet_opt. Normally, tcp_v4_syn_recv_sock() inserts the full |
| socket into ehash and sets NULL to ireq_opt. Otherwise, |
| tcp_v4_syn_recv_sock() has to reset inet_opt by NULL and free the full |
| socket. |
| |
| The commit 01770a1661657 ("tcp: fix race condition when creating child |
| sockets from syncookies") added a new path, in which more than one cores |
| create full sockets for the same SYN cookie. Currently, the core which |
| loses the race frees the full socket without resetting inet_opt, resulting |
| in that both sock_put() and reqsk_put() call kfree() for the same memory: |
| |
| sock_put |
| sk_free |
| __sk_free |
| sk_destruct |
| __sk_destruct |
| sk->sk_destruct/inet_sock_destruct |
| kfree(rcu_dereference_protected(inet->inet_opt, 1)); |
| |
| reqsk_put |
| reqsk_free |
| __reqsk_free |
| req->rsk_ops->destructor/tcp_v4_reqsk_destructor |
| kfree(rcu_dereference_protected(inet_rsk(req)->ireq_opt, 1)); |
| |
| Calling kmalloc() between the double kfree() can lead to use-after-free, so |
| this patch fixes it by setting NULL to inet_opt before sock_put(). |
| |
| As a side note, this kind of issue does not happen for IPv6. This is |
| because tcp_v6_syn_recv_sock() clones both ipv6_opt and pktopts which |
| correspond to ireq_opt in IPv4. |
| |
| Fixes: 01770a166165 ("tcp: fix race condition when creating child sockets from syncookies") |
| CC: Ricardo Dias <rdias@singlestore.com> |
| Signed-off-by: Kuniyuki Iwashima <kuniyu@amazon.co.jp> |
| Reviewed-by: Benjamin Herrenschmidt <benh@amazon.com> |
| Reviewed-by: Eric Dumazet <edumazet@google.com> |
| Link: https://lore.kernel.org/r/20210118055920.82516-1-kuniyu@amazon.co.jp |
| Signed-off-by: Jakub Kicinski <kuba@kernel.org> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| net/ipv4/tcp_ipv4.c | 4 ++-- |
| 1 file changed, 2 insertions(+), 2 deletions(-) |
| |
| --- a/net/ipv4/tcp_ipv4.c |
| +++ b/net/ipv4/tcp_ipv4.c |
| @@ -1503,6 +1503,8 @@ struct sock *tcp_v4_syn_recv_sock(const |
| tcp_move_syn(newtp, req); |
| ireq->ireq_opt = NULL; |
| } else { |
| + newinet->inet_opt = NULL; |
| + |
| if (!req_unhash && found_dup_sk) { |
| /* This code path should only be executed in the |
| * syncookie case only |
| @@ -1510,8 +1512,6 @@ struct sock *tcp_v4_syn_recv_sock(const |
| bh_unlock_sock(newsk); |
| sock_put(newsk); |
| newsk = NULL; |
| - } else { |
| - newinet->inet_opt = NULL; |
| } |
| } |
| return newsk; |