mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH net v3] net: tun: fix race condition between tun_attach and tun_get_user with XDP
@ 2026-10-13  8:37 xietangxin
  2026-10-14  9:18 ` netdev-bot+sashiko
  0 siblings, 1 reply; 2+ messages in thread
From: xietangxin @ 2026-10-13  8:37 UTC (permalink / raw)
  To: Willem de Bruijn, Jason Wang, Andrew Lunn, David S . Miller,
	Eric Dumazet, Jakub Kicinski, Paolo Abeni
  Cc: huyizhen, gaoxingwang, xietangxin, netdev, linux-kernel

Concurrent ioctl and write() operations on the same file descriptor
can trigger a WARN_ONCE in netif_get_rxqueue():

  virt_tun0 received packet on queue 2, but number of RX queues is 2
  WARNING: CPU: 2 PID: 5989 at net/core/dev.c:5494
  Call trace:
   bpf_prog_run_generic_xdp
   netif_receive_generic_xdp
   do_xdp_generic
   tun_get_user
   tun_chr_write_iter

This bug manifests on three paths, all leading to the same WARN:
1. Initial attach (TUNSETIFF): tfile->tun transitions from NULL to
   non-NULL, but is published before tun_set_real_num_queues().

   CPU 0 (tun_attach)            CPU 1 (tun_get_user)
   tfile->queue_index = 2
   rcu_assign_pointer(tfile->tun)
                                 skb_record_rx_queue(skb, 2)
                                 do_xdp_generic()
                                   netif_get_rxqueue()
                                   // index(2) >= real(2) -> WARN
   tun_set_real_num_queues()
     real_num_rx_queues = 3

2. Re-attach (TUNSETQUEUE): tfile->tun isn't cleared by __tun_detach
   with clean=false, so rcu_assign_pointer(tfile->tun, tun) writes the
   same pointer value that readers already observe.

   CPU 0 (re-attach)             CPU 1 (tun_get_user)
   tfile->queue_index = 2
   tun_enable_queue(tfile)
     tfile->detached = NULL
                                 skb_record_rx_queue(skb, 2)
                                 do_xdp_generic()
                                   netif_get_rxqueue()
                                   // index(2) >= real(2) -> WARN
   tun_set_real_num_queues()
     real_num_rx_queues = 3

3. A write() on the detached fd records a stale queue_index
   via skb_record_rx_queue(), then enters do_xdp_generic() which
   calls netif_get_rxqueue(). Since dev->real_num_rx_queues has already
   been shrunk, the stale index is out of bounds.

Fix this by:
a) In tun_attach(), move rcu_assign_pointer(tfile->tun, tun) after
   tun_set_real_num_queues(), so that concurrent readers acquiring
   tfile->tun also see the updated real_num_rx_queues.
b) In tun_attach(), defer tun_enable_queue() and tun_napi_enable()
   to after tun_set_real_num_queues(), keeping tfile->detached
   non-NULL during the re-attach window.
c) In tun_get_user()'s skb_xdp branch, add a tfile->detached check
   at the beginning.

Fixes: 3fe260e00cd0 ("net: tun: record RX queue in skb before do_xdp_generic()")
Cc: stable@vger.kernel.org
Signed-off-by: xietangxin <xietangxin@h-partners.com>
---
v3:
- Fix race condition between re-attach and tun_get_user with XDP (sashiko).
- Fix write on the detached fd with XDP (sashiko).

v2:
- Defer publishing tfile->tun after tun_set_real_num_queues() instead of
  moving numqueues increment, as suggested by Jason Wang, to prevent
  potential TX packet drops.
  https://lore.kernel.org/netdev/20260920030240.443223-1-xietangxin@h-partners.com/
  
v1:
  https://lore.kernel.org/netdev/20260917074149.1799437-1-xietangxin@h-partners.com/
---
 drivers/net/tun.c | 19 +++++++++++++------
 1 file changed, 13 insertions(+), 6 deletions(-)

diff --git a/drivers/net/tun.c b/drivers/net/tun.c
index 242899f7fd07..dcab0aba6705 100644
--- a/drivers/net/tun.c
+++ b/drivers/net/tun.c
@@ -792,10 +792,7 @@ static int tun_attach(struct tun_struct *tun, struct file *file,
 		err = 0;
 	}
 
-	if (tfile->detached) {
-		tun_enable_queue(tfile);
-		tun_napi_enable(tfile);
-	} else {
+	if (!tfile->detached) {
 		sock_hold(&tfile->sk);
 		tun_napi_init(tun, tfile, napi, napi_frags);
 	}
@@ -811,11 +808,16 @@ static int tun_attach(struct tun_struct *tun, struct file *file,
 	 * initialized tfile; otherwise we risk using half-initialized
 	 * object.
 	 */
-	if (publish_tun)
-		rcu_assign_pointer(tfile->tun, tun);
 	rcu_assign_pointer(tun->tfiles[tun->numqueues], tfile);
 	WRITE_ONCE(tun->numqueues, tun->numqueues + 1);
 	tun_set_real_num_queues(tun);
+	if (publish_tun)
+		rcu_assign_pointer(tfile->tun, tun);
+
+	if (tfile->detached) {
+		tun_enable_queue(tfile);
+		tun_napi_enable(tfile);
+	}
 out:
 	return err;
 }
@@ -1960,6 +1962,11 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile,
 		struct bpf_prog *xdp_prog;
 		int ret;
 
+		if (unlikely(tfile->detached)) {
+			err = -EBUSY;
+			goto free_skb;
+		}
+
 		local_bh_disable();
 		rcu_read_lock();
 		xdp_prog = rcu_dereference(tun->xdp_prog);
-- 
2.43.0


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

end of thread, other threads:[~2026-10-14  9:18 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-13  8:37 [PATCH net v3] net: tun: fix race condition between tun_attach and tun_get_user with XDP xietangxin
2026-10-14  9:18 ` netdev-bot+sashiko

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®