[bug report] net: udp_tunnel_nic: reference count leak during network namespace migration
From: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Date: 2026-07-18 12:04:19
Description:
syzbot is reporting a reference count leak when a netdevsim device configured with
UDP tunnel offloads is moved to another network namespace, and then the target
namespace is destroyed.
Cause of the Leak:
The root cause is a structural mismatch between the namespace migration logic in
__dev_change_net_namespace() and the unregistration path in udp_tunnel_nic_unregister().
1. During __dev_change_net_namespace(), it triggers a temporary NETDEV_UNREGISTER event
to flush old configurations:
/* net/core/dev.c: __dev_change_net_namespace() */
----------
/* Notify protocols, that we are about to destroy
* this device. They should clean all the things.
*
* Note that dev->reg_state stays at NETREG_REGISTERED.
* This is wanted because this way 8021q and macvlan know
* the device is just moving and can keep their slaves up.
*/
call_netdevice_notifiers(NETDEV_UNREGISTER, dev);
rcu_barrier(); // <== Proceeds immediately to the new namespace setup without any retries or loops.
----------
2. This event is handled by udp_tunnel_nic_netdevice_event(), which correctly
invokes udp_tunnel_nic_unregister().
3. Inside udp_tunnel_nic_unregister(), there is an asynchronous work-pending check
designed for normal device destruction paths:
/* net/ipv4/udp_tunnel_nic.c: udp_tunnel_nic_unregister() */
----------
/* Wait for the work to be done using the state, netdev core will
* retry unregister until we give up our reference on this device.
*/
if (utn->work_pending)
return;
udp_tunnel_nic_free(utn);
release_dev:
dev->udp_tunnel_nic = NULL;
dev_put(dev); // <== Will not be called if we returned early, for NETDEV_UNREGISTER is fired only once.
----------
For a standard unregister_netdevice() flow, this early return is perfectly fine
because the netdev core loop (netdev_wait_allrefs()) will continually retry
unregistration until all references are dropped.
4. However, __dev_change_net_namespace() does not have a retry loop for NETDEV_UNREGISTER.
It fires the notification exactly once. If utn->work_pending happens to be true at that
precise moment, udp_tunnel_nic_unregister() returns early and silently skips the mandatory
dev_put(dev).
5. Consequently, the old instance's refcount is never decremented, while the device
completes its move and calls NETDEV_REGISTER in the new namespace-eventually leading to
an unfreeable netdev balance when the interface is finally dismantled.
Custom refcount tracker Analysis Summary:
Attached log (obtained using next-20260714 which carries linux-next only patch) shows
an unbalanced +1 from the UDP tunnel subsystem across the namespace migration lifecycle:
* netdevsim3[12]: +1 at udp_tunnel_nic_register (Initial registration)
* netdevsim3[59]: +1 at udp_tunnel_nic_register (Post-migration re-registration)
* netdevsim3[69]: -1 at udp_tunnel_nic_unregister (Final destruction)
* Result: Total sum for udp_tunnel_nic is +1, leaving balance is 1 for the device
registration tracker. The NETDEV_UNREGISTER notice fired inside
__dev_change_net_namespace() was completely skipped due to the early return path.
Reported-by: syzbot+e2af46126e0644cbebdd@syzkaller.appspotmail.com
Analyzed-by: AI mode in Google search (no mail address) Attachments
- 15f44cb9580000.txt [text/plain] 83210 bytes · preview