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

From: xietangxin

Date: Tue Oct 13 2026 - 04:45:34 EST


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@xxxxxxxxxxxxxxx
Signed-off-by: xietangxin <xietangxin@xxxxxxxxxxxxxx>
---
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@xxxxxxxxxxxxxx/

v1:
https://lore.kernel.org/netdev/20260917074149.1799437-1-xietangxin@xxxxxxxxxxxxxx/
---
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