[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