| From 424f7927c1ed0c4a954fc03320368bd8d0e681d5 Mon Sep 17 00:00:00 2001 |
| From: Sasha Levin <sashal@kernel.org> |
| Date: Wed, 9 Jun 2021 11:49:01 +0200 |
| Subject: udp: fix race between close() and udp_abort() |
| |
| From: Paolo Abeni <pabeni@redhat.com> |
| |
| [ Upstream commit a8b897c7bcd47f4147d066e22cc01d1026d7640e ] |
| |
| Kaustubh reported and diagnosed a panic in udp_lib_lookup(). |
| The root cause is udp_abort() racing with close(). Both |
| racing functions acquire the socket lock, but udp{v6}_destroy_sock() |
| release it before performing destructive actions. |
| |
| We can't easily extend the socket lock scope to avoid the race, |
| instead use the SOCK_DEAD flag to prevent udp_abort from doing |
| any action when the critical race happens. |
| |
| Diagnosed-and-tested-by: Kaustubh Pandey <kapandey@codeaurora.org> |
| Fixes: 5d77dca82839 ("net: diag: support SOCK_DESTROY for UDP sockets") |
| Signed-off-by: Paolo Abeni <pabeni@redhat.com> |
| Signed-off-by: David S. Miller <davem@davemloft.net> |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| --- |
| net/ipv4/udp.c | 10 ++++++++++ |
| net/ipv6/udp.c | 3 +++ |
| 2 files changed, 13 insertions(+) |
| |
| diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c |
| index 24841a9e9966..4644f86c932f 100644 |
| --- a/net/ipv4/udp.c |
| +++ b/net/ipv4/udp.c |
| @@ -2511,6 +2511,9 @@ void udp_destroy_sock(struct sock *sk) |
| { |
| struct udp_sock *up = udp_sk(sk); |
| bool slow = lock_sock_fast(sk); |
| + |
| + /* protects from races with udp_abort() */ |
| + sock_set_flag(sk, SOCK_DEAD); |
| udp_flush_pending_frames(sk); |
| unlock_sock_fast(sk, slow); |
| if (static_branch_unlikely(&udp_encap_needed_key)) { |
| @@ -2770,10 +2773,17 @@ int udp_abort(struct sock *sk, int err) |
| { |
| lock_sock(sk); |
| |
| + /* udp{v6}_destroy_sock() sets it under the sk lock, avoid racing |
| + * with close() |
| + */ |
| + if (sock_flag(sk, SOCK_DEAD)) |
| + goto out; |
| + |
| sk->sk_err = err; |
| sk->sk_error_report(sk); |
| __udp_disconnect(sk, 0); |
| |
| +out: |
| release_sock(sk); |
| |
| return 0; |
| diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c |
| index 6762430280f5..3c94b81bb459 100644 |
| --- a/net/ipv6/udp.c |
| +++ b/net/ipv6/udp.c |
| @@ -1539,6 +1539,9 @@ void udpv6_destroy_sock(struct sock *sk) |
| { |
| struct udp_sock *up = udp_sk(sk); |
| lock_sock(sk); |
| + |
| + /* protects from races with udp_abort() */ |
| + sock_set_flag(sk, SOCK_DEAD); |
| udp_v6_flush_pending_frames(sk); |
| release_sock(sk); |
| |
| -- |
| 2.30.2 |
| |