linux-rdma.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v3 net 0/2] tcp/rds: Fix use-after-free around kernel TCP reqsk.
@ 2024-03-06 23:04 Kuniyuki Iwashima
  2024-03-06 23:04 ` [PATCH v3 net 1/2] tcp: Restart iteration after removing reqsk in inet_twsk_purge() Kuniyuki Iwashima
  2024-03-06 23:04 ` [PATCH v3 net 2/2] rds: tcp: Fix use-after-free of net in reqsk_timer_handler() Kuniyuki Iwashima
  0 siblings, 2 replies; 5+ messages in thread
From: Kuniyuki Iwashima @ 2024-03-06 23:04 UTC (permalink / raw)
  To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Allison Henderson
  Cc: Kuniyuki Iwashima, Kuniyuki Iwashima, netdev, linux-rdma, rds-devel

syzkaller reported an warning of netns ref tracker for RDS per-netns
TCP listener, which commit 740ea3c4a0b2 ("tcp: Clean up kernel listener's
reqsk in inet_twsk_purge()") fixed for per-netns ehash.

This series fixes the bug in the partial fix and fixes the bug in the
global ehash.


Changes:
  v3:
    * Drop patch 2, 3, 5
    * Fix UAF by purging reqsk during netns dismantle.

  v2: https://lore.kernel.org/netdev/20240227011041.97375-1-kuniyu@amazon.com/
    * Add patch 1, 3, 5
    * Use __sock_create() instead of converting socket
    * Drop Sowmini from CC as it's bounced (patchwork may complain)

  v1: https://lore.kernel.org/netdev/20240223172448.94084-1-kuniyu@amazon.com/


Kuniyuki Iwashima (2):
  tcp: Restart iteration after removing reqsk in inet_twsk_purge().
  rds: tcp: Fix use-after-free of net in reqsk_timer_handler().

 net/ipv4/inet_timewait_sock.c | 4 +++-
 net/ipv4/tcp_minisocks.c      | 4 ----
 2 files changed, 3 insertions(+), 5 deletions(-)

-- 
2.30.2


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH v3 net 1/2] tcp: Restart iteration after removing reqsk in inet_twsk_purge().
  2024-03-06 23:04 [PATCH v3 net 0/2] tcp/rds: Fix use-after-free around kernel TCP reqsk Kuniyuki Iwashima
@ 2024-03-06 23:04 ` Kuniyuki Iwashima
  2024-03-07  9:51   ` Eric Dumazet
  2024-03-06 23:04 ` [PATCH v3 net 2/2] rds: tcp: Fix use-after-free of net in reqsk_timer_handler() Kuniyuki Iwashima
  1 sibling, 1 reply; 5+ messages in thread
From: Kuniyuki Iwashima @ 2024-03-06 23:04 UTC (permalink / raw)
  To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Allison Henderson
  Cc: Kuniyuki Iwashima, Kuniyuki Iwashima, netdev, linux-rdma, rds-devel

Commit 740ea3c4a0b2 ("tcp: Clean up kernel listener's reqsk in
inet_twsk_purge()") added changes in inet_twsk_purge() to purge
reqsk in per-netns ehash during netns dismantle.

inet_csk_reqsk_queue_drop_and_put() will remove reqsk from per-netns
ehash, but the iteration uses sk_nulls_for_each_rcu(), which is not
safe.

After removing reqsk, we need to restart iteration.

Note that we need not check net->ns.count here because per-netns
ehash does not have reqsk in other live netns.  We will check
net->ns.count in the following patch.

Fixes: 740ea3c4a0b2 ("tcp: Clean up kernel listener's reqsk in inet_twsk_purge()")
Reported-by: Eric Dumazet <edumazet@google.com>
Signed-off-by: Kuniyuki Iwashima <kuniyu@amazon.com>
---
 net/ipv4/inet_timewait_sock.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/net/ipv4/inet_timewait_sock.c b/net/ipv4/inet_timewait_sock.c
index 5befa4de5b24..00cbebaa2c68 100644
--- a/net/ipv4/inet_timewait_sock.c
+++ b/net/ipv4/inet_timewait_sock.c
@@ -287,6 +287,8 @@ void inet_twsk_purge(struct inet_hashinfo *hashinfo, int family)
 					struct request_sock *req = inet_reqsk(sk);
 
 					inet_csk_reqsk_queue_drop_and_put(req->rsk_listener, req);
+
+					goto restart;
 				}
 
 				continue;
-- 
2.30.2


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH v3 net 2/2] rds: tcp: Fix use-after-free of net in reqsk_timer_handler().
  2024-03-06 23:04 [PATCH v3 net 0/2] tcp/rds: Fix use-after-free around kernel TCP reqsk Kuniyuki Iwashima
  2024-03-06 23:04 ` [PATCH v3 net 1/2] tcp: Restart iteration after removing reqsk in inet_twsk_purge() Kuniyuki Iwashima
@ 2024-03-06 23:04 ` Kuniyuki Iwashima
  1 sibling, 0 replies; 5+ messages in thread
From: Kuniyuki Iwashima @ 2024-03-06 23:04 UTC (permalink / raw)
  To: David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Allison Henderson
  Cc: Kuniyuki Iwashima, Kuniyuki Iwashima, netdev, linux-rdma,
	rds-devel, syzkaller

syzkaller reported a warning of netns tracker [0] followed by KASAN
splat [1] and another ref tracker warning [1].

syzkaller could not find a repro, but in the log, the only suspicious
sequence was as follows:

  18:26:22 executing program 1:
  r0 = socket$inet6_mptcp(0xa, 0x1, 0x106)
  ...
  connect$inet6(r0, &(0x7f0000000080)={0xa, 0x4001, 0x0, @loopback}, 0x1c) (async)

The notable thing here is 0x4001 in connect(), which is RDS_TCP_PORT.

So, the scenario would be:

  1. unshare(CLONE_NEWNET) creates a per netns tcp listener in
      rds_tcp_listen_init().
  2. syz-executor connect()s to it and creates a reqsk.
  3. syz-executor exit()s immediately.
  4. netns is dismantled.  [0]
  5. reqsk timer is fired, and UAF happens while freeing reqsk.  [1]
  6. listener is freed after RCU grace period.  [2]

Basically, reqsk assumes that the listener guarantees netns safety
until all reqsk timers are expired by holding the listener's refcount.
However, this was not the case for kernel sockets.

Commit 740ea3c4a0b2 ("tcp: Clean up kernel listener's reqsk in
inet_twsk_purge()") fixed this issue only for per-netns ehash.

Let's apply the same fix for the global ehash.

[0]:
ref_tracker: net notrefcnt@0000000065449cc3 has 1/1 users at
     sk_alloc (./include/net/net_namespace.h:337 net/core/sock.c:2146)
     inet6_create (net/ipv6/af_inet6.c:192 net/ipv6/af_inet6.c:119)
     __sock_create (net/socket.c:1572)
     rds_tcp_listen_init (net/rds/tcp_listen.c:279)
     rds_tcp_init_net (net/rds/tcp.c:577)
     ops_init (net/core/net_namespace.c:137)
     setup_net (net/core/net_namespace.c:340)
     copy_net_ns (net/core/net_namespace.c:497)
     create_new_namespaces (kernel/nsproxy.c:110)
     unshare_nsproxy_namespaces (kernel/nsproxy.c:228 (discriminator 4))
     ksys_unshare (kernel/fork.c:3429)
     __x64_sys_unshare (kernel/fork.c:3496)
     do_syscall_64 (arch/x86/entry/common.c:52 arch/x86/entry/common.c:83)
     entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:129)
...
WARNING: CPU: 0 PID: 27 at lib/ref_tracker.c:179 ref_tracker_dir_exit (lib/ref_tracker.c:179)

[1]:
BUG: KASAN: slab-use-after-free in inet_csk_reqsk_queue_drop (./include/net/inet_hashtables.h:180 net/ipv4/inet_connection_sock.c:952 net/ipv4/inet_connection_sock.c:966)
Read of size 8 at addr ffff88801b370400 by task swapper/0/0
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.16.0-0-gd239552ce722-prebuilt.qemu.org 04/01/2014
Call Trace:
 <IRQ>
 dump_stack_lvl (lib/dump_stack.c:107 (discriminator 1))
 print_report (mm/kasan/report.c:378 mm/kasan/report.c:488)
 kasan_report (mm/kasan/report.c:603)
 inet_csk_reqsk_queue_drop (./include/net/inet_hashtables.h:180 net/ipv4/inet_connection_sock.c:952 net/ipv4/inet_connection_sock.c:966)
 reqsk_timer_handler (net/ipv4/inet_connection_sock.c:979 net/ipv4/inet_connection_sock.c:1092)
 call_timer_fn (./arch/x86/include/asm/jump_label.h:27 ./include/linux/jump_label.h:207 ./include/trace/events/timer.h:127 kernel/time/timer.c:1701)
 __run_timers.part.0 (kernel/time/timer.c:1752 kernel/time/timer.c:2038)
 run_timer_softirq (kernel/time/timer.c:2053)
 __do_softirq (./arch/x86/include/asm/jump_label.h:27 ./include/linux/jump_label.h:207 ./include/trace/events/irq.h:142 kernel/softirq.c:554)
 irq_exit_rcu (kernel/softirq.c:427 kernel/softirq.c:632 kernel/softirq.c:644)
 sysvec_apic_timer_interrupt (arch/x86/kernel/apic/apic.c:1076 (discriminator 14))
 </IRQ>

Allocated by task 258 on cpu 0 at 83.612050s:
 kasan_save_stack (mm/kasan/common.c:48)
 kasan_save_track (mm/kasan/common.c:68)
 __kasan_slab_alloc (mm/kasan/common.c:343)
 kmem_cache_alloc (mm/slub.c:3813 mm/slub.c:3860 mm/slub.c:3867)
 copy_net_ns (./include/linux/slab.h:701 net/core/net_namespace.c:421 net/core/net_namespace.c:480)
 create_new_namespaces (kernel/nsproxy.c:110)
 unshare_nsproxy_namespaces (kernel/nsproxy.c:228 (discriminator 4))
 ksys_unshare (kernel/fork.c:3429)
 __x64_sys_unshare (kernel/fork.c:3496)
 do_syscall_64 (arch/x86/entry/common.c:52 arch/x86/entry/common.c:83)
 entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:129)

Freed by task 27 on cpu 0 at 329.158864s:
 kasan_save_stack (mm/kasan/common.c:48)
 kasan_save_track (mm/kasan/common.c:68)
 kasan_save_free_info (mm/kasan/generic.c:643)
 __kasan_slab_free (mm/kasan/common.c:265)
 kmem_cache_free (mm/slub.c:4299 mm/slub.c:4363)
 cleanup_net (net/core/net_namespace.c:456 net/core/net_namespace.c:446 net/core/net_namespace.c:639)
 process_one_work (kernel/workqueue.c:2638)
 worker_thread (kernel/workqueue.c:2700 kernel/workqueue.c:2787)
 kthread (kernel/kthread.c:388)
 ret_from_fork (arch/x86/kernel/process.c:153)
 ret_from_fork_asm (arch/x86/entry/entry_64.S:250)

The buggy address belongs to the object at ffff88801b370000
 which belongs to the cache net_namespace of size 4352
The buggy address is located 1024 bytes inside of
 freed 4352-byte region [ffff88801b370000, ffff88801b371100)

[2]:
WARNING: CPU: 0 PID: 95 at lib/ref_tracker.c:228 ref_tracker_free (lib/ref_tracker.c:228 (discriminator 1))
Modules linked in:
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.16.0-0-gd239552ce722-prebuilt.qemu.org 04/01/2014
RIP: 0010:ref_tracker_free (lib/ref_tracker.c:228 (discriminator 1))
...
Call Trace:
<IRQ>
 __sk_destruct (./include/net/net_namespace.h:353 net/core/sock.c:2204)
 rcu_core (./arch/x86/include/asm/preempt.h:26 kernel/rcu/tree.c:2165 kernel/rcu/tree.c:2433)
 __do_softirq (./arch/x86/include/asm/jump_label.h:27 ./include/linux/jump_label.h:207 ./include/trace/events/irq.h:142 kernel/softirq.c:554)
 irq_exit_rcu (kernel/softirq.c:427 kernel/softirq.c:632 kernel/softirq.c:644)
 sysvec_apic_timer_interrupt (arch/x86/kernel/apic/apic.c:1076 (discriminator 14))
</IRQ>

Reported-by: syzkaller <syzkaller@googlegroups.com>
Suggested-by: Eric Dumazet <edumazet@google.com>
Fixes: 467fa15356ac ("RDS-TCP: Support multiple RDS-TCP listen endpoints, one per netns.")
Signed-off-by: Kuniyuki Iwashima <kuniyu@amazon.com>
---
 net/ipv4/inet_timewait_sock.c | 2 +-
 net/ipv4/tcp_minisocks.c      | 4 ----
 2 files changed, 1 insertion(+), 5 deletions(-)

diff --git a/net/ipv4/inet_timewait_sock.c b/net/ipv4/inet_timewait_sock.c
index 00cbebaa2c68..961b1917c3eb 100644
--- a/net/ipv4/inet_timewait_sock.c
+++ b/net/ipv4/inet_timewait_sock.c
@@ -283,7 +283,7 @@ void inet_twsk_purge(struct inet_hashinfo *hashinfo, int family)
 				 * freed.  Userspace listener and reqsk never exist here.
 				 */
 				if (unlikely(sk->sk_state == TCP_NEW_SYN_RECV &&
-					     hashinfo->pernet)) {
+					     !refcount_read(&sock_net(sk)->ns.count))) {
 					struct request_sock *req = inet_reqsk(sk);
 
 					inet_csk_reqsk_queue_drop_and_put(req->rsk_listener, req);
diff --git a/net/ipv4/tcp_minisocks.c b/net/ipv4/tcp_minisocks.c
index 9e85f2a0bddd..0ecc7311dc6c 100644
--- a/net/ipv4/tcp_minisocks.c
+++ b/net/ipv4/tcp_minisocks.c
@@ -398,10 +398,6 @@ void tcp_twsk_purge(struct list_head *net_exit_list, int family)
 			/* Even if tw_refcount == 1, we must clean up kernel reqsk */
 			inet_twsk_purge(net->ipv4.tcp_death_row.hashinfo, family);
 		} else if (!purged_once) {
-			/* The last refcount is decremented in tcp_sk_exit_batch() */
-			if (refcount_read(&net->ipv4.tcp_death_row.tw_refcount) == 1)
-				continue;
-
 			inet_twsk_purge(&tcp_hashinfo, family);
 			purged_once = true;
 		}
-- 
2.30.2


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH v3 net 1/2] tcp: Restart iteration after removing reqsk in inet_twsk_purge().
  2024-03-06 23:04 ` [PATCH v3 net 1/2] tcp: Restart iteration after removing reqsk in inet_twsk_purge() Kuniyuki Iwashima
@ 2024-03-07  9:51   ` Eric Dumazet
  2024-03-07 22:44     ` Kuniyuki Iwashima
  0 siblings, 1 reply; 5+ messages in thread
From: Eric Dumazet @ 2024-03-07  9:51 UTC (permalink / raw)
  To: Kuniyuki Iwashima
  Cc: David S. Miller, Jakub Kicinski, Paolo Abeni, Allison Henderson,
	Kuniyuki Iwashima, netdev, linux-rdma, rds-devel

On Thu, Mar 7, 2024 at 12:05 AM Kuniyuki Iwashima <kuniyu@amazon.com> wrote:
>
> Commit 740ea3c4a0b2 ("tcp: Clean up kernel listener's reqsk in
> inet_twsk_purge()") added changes in inet_twsk_purge() to purge
> reqsk in per-netns ehash during netns dismantle.
>
> inet_csk_reqsk_queue_drop_and_put() will remove reqsk from per-netns
> ehash, but the iteration uses sk_nulls_for_each_rcu(), which is not
> safe.
>
> After removing reqsk, we need to restart iteration.
>
> Note that we need not check net->ns.count here because per-netns
> ehash does not have reqsk in other live netns.  We will check
> net->ns.count in the following patch.
>
> Fixes: 740ea3c4a0b2 ("tcp: Clean up kernel listener's reqsk in inet_twsk_purge()")
> Reported-by: Eric Dumazet <edumazet@google.com>
> Signed-off-by: Kuniyuki Iwashima <kuniyu@amazon.com>
> ---
>  net/ipv4/inet_timewait_sock.c | 2 ++
>  1 file changed, 2 insertions(+)
>
> diff --git a/net/ipv4/inet_timewait_sock.c b/net/ipv4/inet_timewait_sock.c
> index 5befa4de5b24..00cbebaa2c68 100644
> --- a/net/ipv4/inet_timewait_sock.c
> +++ b/net/ipv4/inet_timewait_sock.c
> @@ -287,6 +287,8 @@ void inet_twsk_purge(struct inet_hashinfo *hashinfo, int family)
>                                         struct request_sock *req = inet_reqsk(sk);
>
>                                         inet_csk_reqsk_queue_drop_and_put(req->rsk_listener, req);
> +
> +                                       goto restart;
>                                 }
>
>                                 continue;

Note how the RCU rules that I followed for TCP_TIME_WAIT made
me to grab a reference on tw->tw_refcnt, using refcount_inc_not_zero()

I think your code had multiple bugs, because
inet_csk_reqsk_queue_drop_and_put() could cause UAF
if the timer already fired and refcount went to zero already.

We also could add sk_nulls_for_each_rcu_safe() to avoid these pesky
"goto restart;"

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH v3 net 1/2] tcp: Restart iteration after removing reqsk in inet_twsk_purge().
  2024-03-07  9:51   ` Eric Dumazet
@ 2024-03-07 22:44     ` Kuniyuki Iwashima
  0 siblings, 0 replies; 5+ messages in thread
From: Kuniyuki Iwashima @ 2024-03-07 22:44 UTC (permalink / raw)
  To: edumazet
  Cc: allison.henderson, davem, kuba, kuni1840, kuniyu, linux-rdma,
	netdev, pabeni, rds-devel

From: Eric Dumazet <edumazet@google.com>
Date: Thu, 7 Mar 2024 10:51:15 +0100
> On Thu, Mar 7, 2024 at 12:05 AM Kuniyuki Iwashima <kuniyu@amazon.com> wrote:
> >
> > Commit 740ea3c4a0b2 ("tcp: Clean up kernel listener's reqsk in
> > inet_twsk_purge()") added changes in inet_twsk_purge() to purge
> > reqsk in per-netns ehash during netns dismantle.
> >
> > inet_csk_reqsk_queue_drop_and_put() will remove reqsk from per-netns
> > ehash, but the iteration uses sk_nulls_for_each_rcu(), which is not
> > safe.
> >
> > After removing reqsk, we need to restart iteration.
> >
> > Note that we need not check net->ns.count here because per-netns
> > ehash does not have reqsk in other live netns.  We will check
> > net->ns.count in the following patch.
> >
> > Fixes: 740ea3c4a0b2 ("tcp: Clean up kernel listener's reqsk in inet_twsk_purge()")
> > Reported-by: Eric Dumazet <edumazet@google.com>
> > Signed-off-by: Kuniyuki Iwashima <kuniyu@amazon.com>
> > ---
> >  net/ipv4/inet_timewait_sock.c | 2 ++
> >  1 file changed, 2 insertions(+)
> >
> > diff --git a/net/ipv4/inet_timewait_sock.c b/net/ipv4/inet_timewait_sock.c
> > index 5befa4de5b24..00cbebaa2c68 100644
> > --- a/net/ipv4/inet_timewait_sock.c
> > +++ b/net/ipv4/inet_timewait_sock.c
> > @@ -287,6 +287,8 @@ void inet_twsk_purge(struct inet_hashinfo *hashinfo, int family)
> >                                         struct request_sock *req = inet_reqsk(sk);
> >
> >                                         inet_csk_reqsk_queue_drop_and_put(req->rsk_listener, req);
> > +
> > +                                       goto restart;
> >                                 }
> >
> >                                 continue;
> 
> Note how the RCU rules that I followed for TCP_TIME_WAIT made
> me to grab a reference on tw->tw_refcnt, using refcount_inc_not_zero()
> 
> I think your code had multiple bugs, because
> inet_csk_reqsk_queue_drop_and_put() could cause UAF
> if the timer already fired and refcount went to zero already.

Ugh.. exactly.
I'll post v4 following the TIME_WAIT path.

---8<---
diff --git a/net/ipv4/inet_timewait_sock.c b/net/ipv4/inet_timewait_sock.c
index 961b1917c3eb..c81f83893fc7 100644
--- a/net/ipv4/inet_timewait_sock.c
+++ b/net/ipv4/inet_timewait_sock.c
@@ -278,20 +278,32 @@ void inet_twsk_purge(struct inet_hashinfo *hashinfo, int family)
 restart:
 		sk_nulls_for_each_rcu(sk, node, &head->chain) {
 			if (sk->sk_state != TCP_TIME_WAIT) {
+				struct request_sock *req;
+
+				if (likely(sk->sk_state != TCP_NEW_SYN_RECV))
+					continue;
+
 				/* A kernel listener socket might not hold refcnt for net,
 				 * so reqsk_timer_handler() could be fired after net is
 				 * freed.  Userspace listener and reqsk never exist here.
 				 */
-				if (unlikely(sk->sk_state == TCP_NEW_SYN_RECV &&
-					     !refcount_read(&sock_net(sk)->ns.count))) {
-					struct request_sock *req = inet_reqsk(sk);
 
-					inet_csk_reqsk_queue_drop_and_put(req->rsk_listener, req);
+				if (sk->sk_family != family ||
+				    refcount_read(&sock_net(sk)->ns.count))
+					continue;
+
+				req = inet_reqsk(sk);
+				if (unlikely(!refcount_inc_not_zero(&req->rsk_refcnt)))
+					continue;
 
-					goto restart;
+				if (unlikely(sk->sk_family != family ||
+					     refcount_read(&sock_net(sk)->ns.count))) {
+					reqsk_put(req);
+					continue;
 				}
 
-				continue;
+				inet_csk_reqsk_queue_drop_and_put(req->rsk_listener, req);
+				goto restart;
 			}
 
 			tw = inet_twsk(sk);
---8<---


> 
> We also could add sk_nulls_for_each_rcu_safe() to avoid these pesky
> "goto restart;"

I'll post this followup for net-next in the next release cycle.

Thanks!

^ permalink raw reply related	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2024-03-07 22:44 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-03-06 23:04 [PATCH v3 net 0/2] tcp/rds: Fix use-after-free around kernel TCP reqsk Kuniyuki Iwashima
2024-03-06 23:04 ` [PATCH v3 net 1/2] tcp: Restart iteration after removing reqsk in inet_twsk_purge() Kuniyuki Iwashima
2024-03-07  9:51   ` Eric Dumazet
2024-03-07 22:44     ` Kuniyuki Iwashima
2024-03-06 23:04 ` [PATCH v3 net 2/2] rds: tcp: Fix use-after-free of net in reqsk_timer_handler() Kuniyuki Iwashima

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).