As a defensive measure, guard netdev_put() and netdev_ref_replace() against refcount corruption when ref_tracker_free() returns -EINVAL (tracker already freed). Currently __dev_put() is called unconditionally, which over-decrements the device refcount on a double-free and can cause netdev_wait_allrefs_any() to return prematurely. Change netdev_tracker_free() to return int so callers can detect the -EINVAL case. In netdev_put() and netdev_ref_replace(), skip __dev_put() when the tracker was already freed; ref_tracker_free() has already warned and the first successful free already decremented the refcount. The -EEXIST case (NULL tracker, untracked reference) still calls __dev_put(), preserving existing behavior. Fixes: f12bf6f3f942 ("net: watchdog: add net device refcount tracker") Cc: stable@vger.kernel.org Signed-off-by: Jiangshan Yi --- include/linux/netdevice.h | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 9981d63..5e8cf3f 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -4512,11 +4512,13 @@ static inline void netdev_tracker_alloc(struct net_device *dev, #endif } -static inline void netdev_tracker_free(struct net_device *dev, +static inline int netdev_tracker_free(struct net_device *dev, netdevice_tracker *tracker) { #ifdef CONFIG_NET_DEV_REFCNT_TRACKER - ref_tracker_free(&dev->refcnt_tracker, tracker); + return ref_tracker_free(&dev->refcnt_tracker, tracker); +#else + return 0; #endif } @@ -4533,8 +4535,12 @@ static inline void netdev_put(struct net_device *dev, netdevice_tracker *tracker) { if (dev) { - netdev_tracker_free(dev, tracker); - __dev_put(dev); + /* Skip __dev_put() on -EINVAL: ref_tracker_free() already + * warned and the refcount was decremented by the first + * successful free. + */ + if (netdev_tracker_free(dev, tracker) != -EINVAL) + __dev_put(dev); } } @@ -4569,11 +4575,10 @@ static inline void netdev_ref_replace(struct net_device *odev, netdevice_tracker *tracker, gfp_t gfp) { - if (odev) - netdev_tracker_free(odev, tracker); + if (odev && netdev_tracker_free(odev, tracker) != -EINVAL) + __dev_put(odev); __dev_hold(ndev); - __dev_put(odev); if (ndev) __netdev_tracker_alloc(ndev, tracker, gfp); -- 2.25.1