Patch "tcp: prevent concurrent execution of tcp_sk_exit_batch" has been added to the 6.10-stable tree

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This is a note to let you know that I've just added the patch titled

    tcp: prevent concurrent execution of tcp_sk_exit_batch

to the 6.10-stable tree which can be found at:
    http://www.kernel.org/git/?p=linux/kernel/git/stable/stable-queue.git;a=summary

The filename of the patch is:
     tcp-prevent-concurrent-execution-of-tcp_sk_exit_batc.patch
and it can be found in the queue-6.10 subdirectory.

If you, or anyone else, feels it should not be added to the stable tree,
please let <stable@xxxxxxxxxxxxxxx> know about it.



commit eb6ee959c67ab0e02e6d4d5cca23715bcb918c8f
Author: Florian Westphal <fw@xxxxxxxxx>
Date:   Tue Aug 13 00:28:25 2024 +0200

    tcp: prevent concurrent execution of tcp_sk_exit_batch
    
    [ Upstream commit 565d121b69980637f040eb4d84289869cdaabedf ]
    
    Its possible that two threads call tcp_sk_exit_batch() concurrently,
    once from the cleanup_net workqueue, once from a task that failed to clone
    a new netns.  In the latter case, error unwinding calls the exit handlers
    in reverse order for the 'failed' netns.
    
    tcp_sk_exit_batch() calls tcp_twsk_purge().
    Problem is that since commit b099ce2602d8 ("net: Batch inet_twsk_purge"),
    this function picks up twsk in any dying netns, not just the one passed
    in via exit_batch list.
    
    This means that the error unwind of setup_net() can "steal" and destroy
    timewait sockets belonging to the exiting netns.
    
    This allows the netns exit worker to proceed to call
    
    WARN_ON_ONCE(!refcount_dec_and_test(&net->ipv4.tcp_death_row.tw_refcount));
    
    without the expected 1 -> 0 transition, which then splats.
    
    At same time, error unwind path that is also running inet_twsk_purge()
    will splat as well:
    
    WARNING: .. at lib/refcount.c:31 refcount_warn_saturate+0x1ed/0x210
    ...
     refcount_dec include/linux/refcount.h:351 [inline]
     inet_twsk_kill+0x758/0x9c0 net/ipv4/inet_timewait_sock.c:70
     inet_twsk_deschedule_put net/ipv4/inet_timewait_sock.c:221
     inet_twsk_purge+0x725/0x890 net/ipv4/inet_timewait_sock.c:304
     tcp_sk_exit_batch+0x1c/0x170 net/ipv4/tcp_ipv4.c:3522
     ops_exit_list+0x128/0x180 net/core/net_namespace.c:178
     setup_net+0x714/0xb40 net/core/net_namespace.c:375
     copy_net_ns+0x2f0/0x670 net/core/net_namespace.c:508
     create_new_namespaces+0x3ea/0xb10 kernel/nsproxy.c:110
    
    ... because refcount_dec() of tw_refcount unexpectedly dropped to 0.
    
    This doesn't seem like an actual bug (no tw sockets got lost and I don't
    see a use-after-free) but as erroneous trigger of debug check.
    
    Add a mutex to force strict ordering: the task that calls tcp_twsk_purge()
    blocks other task from doing final _dec_and_test before mutex-owner has
    removed all tw sockets of dying netns.
    
    Fixes: e9bd0cca09d1 ("tcp: Don't allocate tcp_death_row outside of struct netns_ipv4.")
    Reported-by: syzbot+8ea26396ff85d23a8929@xxxxxxxxxxxxxxxxxxxxxxxxx
    Closes: https://lore.kernel.org/netdev/0000000000003a5292061f5e4e19@xxxxxxxxxx/
    Link: https://lore.kernel.org/netdev/20240812140104.GA21559@xxxxxxxxxxxxx/
    Signed-off-by: Florian Westphal <fw@xxxxxxxxx>
    Reviewed-by: Kuniyuki Iwashima <kuniyu@xxxxxxxxxx>
    Reviewed-by: Jason Xing <kerneljasonxing@xxxxxxxxx>
    Reviewed-by: Eric Dumazet <edumazet@xxxxxxxxxx>
    Link: https://patch.msgid.link/20240812222857.29837-1-fw@xxxxxxxxx
    Signed-off-by: Jakub Kicinski <kuba@xxxxxxxxxx>
    Signed-off-by: Sasha Levin <sashal@xxxxxxxxxx>

diff --git a/net/ipv4/tcp_ipv4.c b/net/ipv4/tcp_ipv4.c
index a541659b6562b..8f8f93716ff85 100644
--- a/net/ipv4/tcp_ipv4.c
+++ b/net/ipv4/tcp_ipv4.c
@@ -95,6 +95,8 @@ EXPORT_SYMBOL(tcp_hashinfo);
 
 static DEFINE_PER_CPU(struct sock *, ipv4_tcp_sk);
 
+static DEFINE_MUTEX(tcp_exit_batch_mutex);
+
 static u32 tcp_v4_init_seq(const struct sk_buff *skb)
 {
 	return secure_tcp_seq(ip_hdr(skb)->daddr,
@@ -3509,6 +3511,16 @@ static void __net_exit tcp_sk_exit_batch(struct list_head *net_exit_list)
 {
 	struct net *net;
 
+	/* make sure concurrent calls to tcp_sk_exit_batch from net_cleanup_work
+	 * and failed setup_net error unwinding path are serialized.
+	 *
+	 * tcp_twsk_purge() handles twsk in any dead netns, not just those in
+	 * net_exit_list, the thread that dismantles a particular twsk must
+	 * do so without other thread progressing to refcount_dec_and_test() of
+	 * tcp_death_row.tw_refcount.
+	 */
+	mutex_lock(&tcp_exit_batch_mutex);
+
 	tcp_twsk_purge(net_exit_list);
 
 	list_for_each_entry(net, net_exit_list, exit_list) {
@@ -3516,6 +3528,8 @@ static void __net_exit tcp_sk_exit_batch(struct list_head *net_exit_list)
 		WARN_ON_ONCE(!refcount_dec_and_test(&net->ipv4.tcp_death_row.tw_refcount));
 		tcp_fastopen_ctx_destroy(net);
 	}
+
+	mutex_unlock(&tcp_exit_batch_mutex);
 }
 
 static struct pernet_operations __net_initdata tcp_sk_ops = {




[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Index of Archives]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux