in_dev_get() reads dev->ip_ptr under RCU and then unconditionally increments its refcount. inetdev_destroy() clears the pointer and drops the last reference under RTNL, with no grace period in between, so a reader that fetched the pointer before the store can increment a refcount that has already reached zero. That resurrects an object whose RCU free is queued: dropping the resurrected reference re-enters in_dev_finish_destroy() for a second netdev_put() and a second call_rcu() on the same rcu_head, and if the grace period elapses first the drop itself is a use-after-free. inet_netconf_get_devconf() is registered RTNL_FLAG_DOIT_UNLOCKED, and rtnetlink_rcv_msg() exempts RTNL_KIND_GET from the CAP_NET_ADMIN check, so an unprivileged user can drive the reader side. Reproduced as UID 65534 on v7.2-rc7: refcount_t: addition on 0; use-after-free. WARNING: lib/refcount.c:25 at refcount_warn_saturate+0x14c/0x180 CPU: 0 UID: 65534 PID: 655 Comm: j1_poc refcount_warn_saturate+0x14c/0x180 (P) inet_netconf_get_devconf+0x4b0/0x4c4 rtnetlink_rcv_msg+0x434/0x4d0 followed by the matching underflow when the reference is dropped. Use refcount_inc_not_zero() and return NULL for an in_device that has already reached zero. All callers already handle a NULL return, which in_dev_get() gives today whenever dev->ip_ptr is NULL. Callers under RTNL see no change: ip_ptr is cleared before the last put, so a non-NULL ip_ptr there implies a non-zero refcount. Discovered by XBOW, triaged by Baul Lee Fixes: bbcf91053bb6 ("inet: do not use RTNL in inet_netconf_get_devconf()") Signed-off-by: Baul Lee --- include/linux/inetdevice.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/linux/inetdevice.h b/include/linux/inetdevice.h index 6032eea2539a..a1446da64200 100644 --- a/include/linux/inetdevice.h +++ b/include/linux/inetdevice.h @@ -245,8 +245,8 @@ static inline struct in_device *in_dev_get(const struct net_device *dev) rcu_read_lock(); in_dev = __in_dev_get_rcu(dev); - if (in_dev) - refcount_inc(&in_dev->refcnt); + if (in_dev && !refcount_inc_not_zero(&in_dev->refcnt)) + in_dev = NULL; rcu_read_unlock(); return in_dev; } -- 2.50.1