[PATCH net] ipv4: Fix in_device refcount resurrection in in_dev_get()

Baul Lee <[email protected]>
Newsgroups org.kernel.vger.netdev,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
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 <[email protected]>

Fixes: bbcf91053bb6 ("inet: do not use RTNL in inet_netconf_get_devconf()")
Signed-off-by: Baul Lee <[email protected]>
---
 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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.