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 --- 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