[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

Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help