ipv4: use RCU protection in inet_netconf_get_devconf()

inet_netconf_get_devconf() runs without RTNL. It uses dev_get_by_index()
and in_dev_get() to pin the device and its in_device while the reply is
allocated and filled.

in_dev_get() is not safe for a lockless caller. Since commit 9d40c84cf5
("net: devinet: Reduce refcount before grace period"), inetdev_destroy()
drops the final reference before the RCU grace period. A reader can load
dev->ip_ptr, then call refcount_inc() on a zero refcount after
in_dev_free_rcu() has been queued, and keep using the in_device once it
has been freed.

None of these references are needed. An in_device is freed after an RCU
grace period, ipv4_devconf is embedded in it, and
inet_netconf_fill_devconf() does not sleep.

Allocate the skb first, then perform the lookup and the fill under
rcu_read_lock(), using dev_get_by_index_rcu() and __in_dev_get_rcu(),
like inet_netconf_dump_devconf() already does.

Fixes: bbcf91053b ("inet: do not use RTNL in inet_netconf_get_devconf()")
Reported-by: Baul Lee <baul.lee@xbow.com>
Closes: https://lore.kernel.org/netdev/20260815172032.79740-1-baul.lee@xbow.com/
Signed-off-by: Eric Dumazet <edumazet@kernel.org>
Tested-by: Cen Zhang <zzzccc427@gmail.com>
Reviewed-by: Ido Schimmel <idosch@nvidia.com>
Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
Link: https://patch.msgid.link/CAAVpQUA5FEMWV1R4vy2+uzQc+_iLDaz4m-D6kfKYngGf2sE0Eg@mail.gmail.com
Link: https://patch.msgid.link/20261009074906.729877-1-edumazet@kernel.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Eric Dumazet 2026-10-09 09:49:05 +02:00 committed by Jakub Kicinski
parent 4fc1d5bb44
commit b2039a6dd3

View File

@ -2379,8 +2379,8 @@ static int inet_netconf_get_devconf(struct sk_buff *in_skb,
struct net *net = sock_net(in_skb->sk);
struct nlattr *tb[NETCONFA_MAX + 1];
const struct ipv4_devconf *devconf;
struct in_device *in_dev = NULL;
struct net_device *dev = NULL;
struct in_device *in_dev;
struct net_device *dev;
struct sk_buff *skb;
int ifindex;
int err;
@ -2392,7 +2392,13 @@ static int inet_netconf_get_devconf(struct sk_buff *in_skb,
if (!tb[NETCONFA_IFINDEX])
return -EINVAL;
skb = nlmsg_new(inet_netconf_msgsize_devconf(NETCONFA_ALL), GFP_KERNEL);
if (!skb)
return -ENOBUFS;
ifindex = nla_get_s32(tb[NETCONFA_IFINDEX]);
rcu_read_lock();
switch (ifindex) {
case NETCONFA_IFINDEX_ALL:
devconf = net->ipv4.devconf_all;
@ -2402,36 +2408,29 @@ static int inet_netconf_get_devconf(struct sk_buff *in_skb,
break;
default:
err = -ENODEV;
dev = dev_get_by_index(net, ifindex);
if (dev)
in_dev = in_dev_get(dev);
dev = dev_get_by_index_rcu(net, ifindex);
if (!dev)
goto out_unlock;
in_dev = __in_dev_get_rcu(dev);
if (!in_dev)
goto errout;
goto out_unlock;
devconf = &in_dev->cnf;
break;
}
err = -ENOBUFS;
skb = nlmsg_new(inet_netconf_msgsize_devconf(NETCONFA_ALL), GFP_KERNEL);
if (!skb)
goto errout;
err = inet_netconf_fill_devconf(skb, ifindex, devconf,
NETLINK_CB(in_skb).portid,
nlh->nlmsg_seq, RTM_NEWNETCONF, 0,
NETCONFA_ALL);
out_unlock:
rcu_read_unlock();
if (err < 0) {
/* -EMSGSIZE implies BUG in inet_netconf_msgsize_devconf() */
WARN_ON(err == -EMSGSIZE);
kfree_skb(skb);
goto errout;
return err;
}
err = rtnl_unicast(skb, net, NETLINK_CB(in_skb).portid);
errout:
if (in_dev)
in_dev_put(in_dev);
dev_put(dev);
return err;
return rtnl_unicast(skb, net, NETLINK_CB(in_skb).portid);
}
static int inet_netconf_dump_devconf(struct sk_buff *skb,