Thread (2 messages) 2 messages, 2 authors, 1d ago

[PATCH net v3] net: tun: fix race condition between tun_attach and tun_get_user with XDP

flat view
WARM1d

From: xietangxin <hidden>
Date: 2026-10-13 08:27:25
Also in: lkml
Subsystem: networking drivers, the rest, tun/tap driver · Maintainers: Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds, Willem de Bruijn, Jason Wang

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 <redacted>
---
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/ (local)
  
v1:
  https://lore.kernel.org/netdev/20260917074149.1799437-1-xietangxin@h-partners.com/ (local)
---
 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
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help