[PATCH net 2/2] net: guard refcount against tracker double-free
From: Jiangshan Yi
Date: Thu Jul 30 2026 - 23:56:21 EST
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@xxxxxxxxxxxxxxx
Signed-off-by: Jiangshan Yi <yijiangshan@xxxxxxxxxx>
---
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