Commit 5165c48e authored by Jakub Kicinski's avatar Jakub Kicinski

Merge branch 'arp-random-clean-up-and-rcu-conversion-for-ioctl-siocgarp'

Kuniyuki Iwashima says:

====================
arp: Random clean up and RCU conversion for ioctl(SIOCGARP).

arp_ioctl() holds rtnl_lock() regardless of cmd (SIOCDARP, SIOCSARP,
and SIOCGARP) to get net_device by __dev_get_by_name() and copy
dev->name safely.

In the SIOCGARP path, arp_req_get() calls neigh_lookup(), which looks
up a neighbour entry under RCU.

This series cleans up ioctl() code a bit and extends the RCU section
not to take rtnl_lock() and instead use dev_get_by_name_rcu() and
netdev_copy_name() for SIOCGARP.

v2: https://lore.kernel.org/netdev/20240425170002.68160-1-kuniyu@amazon.com/
v1: https://lore.kernel.org/netdev/20240422194755.4221-1-kuniyu@amazon.com/
====================

Link: https://lore.kernel.org/r/20240430015813.71143-1-kuniyu@amazon.comSigned-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parents 496bc586 bf4ea588
...@@ -3136,6 +3136,7 @@ struct net_device *netdev_get_by_name(struct net *net, const char *name, ...@@ -3136,6 +3136,7 @@ struct net_device *netdev_get_by_name(struct net *net, const char *name,
netdevice_tracker *tracker, gfp_t gfp); netdevice_tracker *tracker, gfp_t gfp);
struct net_device *dev_get_by_index_rcu(struct net *net, int ifindex); struct net_device *dev_get_by_index_rcu(struct net *net, int ifindex);
struct net_device *dev_get_by_napi_id(unsigned int napi_id); struct net_device *dev_get_by_napi_id(unsigned int napi_id);
void netdev_copy_name(struct net_device *dev, char *name);
static inline int dev_hard_header(struct sk_buff *skb, struct net_device *dev, static inline int dev_hard_header(struct sk_buff *skb, struct net_device *dev,
unsigned short type, unsigned short type,
......
...@@ -940,6 +940,18 @@ struct net_device *dev_get_by_napi_id(unsigned int napi_id) ...@@ -940,6 +940,18 @@ struct net_device *dev_get_by_napi_id(unsigned int napi_id)
} }
EXPORT_SYMBOL(dev_get_by_napi_id); EXPORT_SYMBOL(dev_get_by_napi_id);
static DEFINE_SEQLOCK(netdev_rename_lock);
void netdev_copy_name(struct net_device *dev, char *name)
{
unsigned int seq;
do {
seq = read_seqbegin(&netdev_rename_lock);
strscpy(name, dev->name, IFNAMSIZ);
} while (read_seqretry(&netdev_rename_lock, seq));
}
/** /**
* netdev_get_name - get a netdevice name, knowing its ifindex. * netdev_get_name - get a netdevice name, knowing its ifindex.
* @net: network namespace * @net: network namespace
...@@ -951,7 +963,6 @@ int netdev_get_name(struct net *net, char *name, int ifindex) ...@@ -951,7 +963,6 @@ int netdev_get_name(struct net *net, char *name, int ifindex)
struct net_device *dev; struct net_device *dev;
int ret; int ret;
down_read(&devnet_rename_sem);
rcu_read_lock(); rcu_read_lock();
dev = dev_get_by_index_rcu(net, ifindex); dev = dev_get_by_index_rcu(net, ifindex);
...@@ -960,12 +971,11 @@ int netdev_get_name(struct net *net, char *name, int ifindex) ...@@ -960,12 +971,11 @@ int netdev_get_name(struct net *net, char *name, int ifindex)
goto out; goto out;
} }
strcpy(name, dev->name); netdev_copy_name(dev, name);
ret = 0; ret = 0;
out: out:
rcu_read_unlock(); rcu_read_unlock();
up_read(&devnet_rename_sem);
return ret; return ret;
} }
...@@ -1217,7 +1227,10 @@ int dev_change_name(struct net_device *dev, const char *newname) ...@@ -1217,7 +1227,10 @@ int dev_change_name(struct net_device *dev, const char *newname)
memcpy(oldname, dev->name, IFNAMSIZ); memcpy(oldname, dev->name, IFNAMSIZ);
write_seqlock(&netdev_rename_lock);
err = dev_get_valid_name(net, dev, newname); err = dev_get_valid_name(net, dev, newname);
write_sequnlock(&netdev_rename_lock);
if (err < 0) { if (err < 0) {
up_write(&devnet_rename_sem); up_write(&devnet_rename_sem);
return err; return err;
...@@ -1257,7 +1270,9 @@ int dev_change_name(struct net_device *dev, const char *newname) ...@@ -1257,7 +1270,9 @@ int dev_change_name(struct net_device *dev, const char *newname)
if (err >= 0) { if (err >= 0) {
err = ret; err = ret;
down_write(&devnet_rename_sem); down_write(&devnet_rename_sem);
write_seqlock(&netdev_rename_lock);
memcpy(dev->name, oldname, IFNAMSIZ); memcpy(dev->name, oldname, IFNAMSIZ);
write_sequnlock(&netdev_rename_lock);
memcpy(oldname, newname, IFNAMSIZ); memcpy(oldname, newname, IFNAMSIZ);
WRITE_ONCE(dev->name_assign_type, old_assign_type); WRITE_ONCE(dev->name_assign_type, old_assign_type);
old_assign_type = NET_NAME_RENAMED; old_assign_type = NET_NAME_RENAMED;
...@@ -11403,8 +11418,12 @@ int __dev_change_net_namespace(struct net_device *dev, struct net *net, ...@@ -11403,8 +11418,12 @@ int __dev_change_net_namespace(struct net_device *dev, struct net *net,
dev_net_set(dev, net); dev_net_set(dev, net);
dev->ifindex = new_ifindex; dev->ifindex = new_ifindex;
if (new_name[0]) /* Rename the netdev to prepared name */ if (new_name[0]) {
/* Rename the netdev to prepared name */
write_seqlock(&netdev_rename_lock);
strscpy(dev->name, new_name, IFNAMSIZ); strscpy(dev->name, new_name, IFNAMSIZ);
write_sequnlock(&netdev_rename_lock);
}
/* Fixup kobjects */ /* Fixup kobjects */
dev_set_uevent_suppress(&dev->dev, 1); dev_set_uevent_suppress(&dev->dev, 1);
......
...@@ -1003,6 +1003,55 @@ static int arp_rcv(struct sk_buff *skb, struct net_device *dev, ...@@ -1003,6 +1003,55 @@ static int arp_rcv(struct sk_buff *skb, struct net_device *dev,
* User level interface (ioctl) * User level interface (ioctl)
*/ */
static struct net_device *arp_req_dev_by_name(struct net *net, struct arpreq *r,
bool getarp)
{
struct net_device *dev;
if (getarp)
dev = dev_get_by_name_rcu(net, r->arp_dev);
else
dev = __dev_get_by_name(net, r->arp_dev);
if (!dev)
return ERR_PTR(-ENODEV);
/* Mmmm... It is wrong... ARPHRD_NETROM == 0 */
if (!r->arp_ha.sa_family)
r->arp_ha.sa_family = dev->type;
if ((r->arp_flags & ATF_COM) && r->arp_ha.sa_family != dev->type)
return ERR_PTR(-EINVAL);
return dev;
}
static struct net_device *arp_req_dev(struct net *net, struct arpreq *r)
{
struct net_device *dev;
struct rtable *rt;
__be32 ip;
if (r->arp_dev[0])
return arp_req_dev_by_name(net, r, false);
if (r->arp_flags & ATF_PUBL)
return NULL;
ip = ((struct sockaddr_in *)&r->arp_pa)->sin_addr.s_addr;
rt = ip_route_output(net, ip, 0, 0, 0, RT_SCOPE_LINK);
if (IS_ERR(rt))
return ERR_CAST(rt);
dev = rt->dst.dev;
ip_rt_put(rt);
if (!dev)
return ERR_PTR(-EINVAL);
return dev;
}
/* /*
* Set (create) an ARP cache entry. * Set (create) an ARP cache entry.
*/ */
...@@ -1023,11 +1072,8 @@ static int arp_req_set_proxy(struct net *net, struct net_device *dev, int on) ...@@ -1023,11 +1072,8 @@ static int arp_req_set_proxy(struct net *net, struct net_device *dev, int on)
static int arp_req_set_public(struct net *net, struct arpreq *r, static int arp_req_set_public(struct net *net, struct arpreq *r,
struct net_device *dev) struct net_device *dev)
{ {
__be32 ip = ((struct sockaddr_in *)&r->arp_pa)->sin_addr.s_addr;
__be32 mask = ((struct sockaddr_in *)&r->arp_netmask)->sin_addr.s_addr; __be32 mask = ((struct sockaddr_in *)&r->arp_netmask)->sin_addr.s_addr;
if (mask && mask != htonl(0xFFFFFFFF))
return -EINVAL;
if (!dev && (r->arp_flags & ATF_COM)) { if (!dev && (r->arp_flags & ATF_COM)) {
dev = dev_getbyhwaddr_rcu(net, r->arp_ha.sa_family, dev = dev_getbyhwaddr_rcu(net, r->arp_ha.sa_family,
r->arp_ha.sa_data); r->arp_ha.sa_data);
...@@ -1035,6 +1081,8 @@ static int arp_req_set_public(struct net *net, struct arpreq *r, ...@@ -1035,6 +1081,8 @@ static int arp_req_set_public(struct net *net, struct arpreq *r,
return -ENODEV; return -ENODEV;
} }
if (mask) { if (mask) {
__be32 ip = ((struct sockaddr_in *)&r->arp_pa)->sin_addr.s_addr;
if (!pneigh_lookup(&arp_tbl, net, &ip, dev, 1)) if (!pneigh_lookup(&arp_tbl, net, &ip, dev, 1))
return -ENOBUFS; return -ENOBUFS;
return 0; return 0;
...@@ -1043,30 +1091,20 @@ static int arp_req_set_public(struct net *net, struct arpreq *r, ...@@ -1043,30 +1091,20 @@ static int arp_req_set_public(struct net *net, struct arpreq *r,
return arp_req_set_proxy(net, dev, 1); return arp_req_set_proxy(net, dev, 1);
} }
static int arp_req_set(struct net *net, struct arpreq *r, static int arp_req_set(struct net *net, struct arpreq *r)
struct net_device *dev)
{ {
__be32 ip;
struct neighbour *neigh; struct neighbour *neigh;
struct net_device *dev;
__be32 ip;
int err; int err;
dev = arp_req_dev(net, r);
if (IS_ERR(dev))
return PTR_ERR(dev);
if (r->arp_flags & ATF_PUBL) if (r->arp_flags & ATF_PUBL)
return arp_req_set_public(net, r, dev); return arp_req_set_public(net, r, dev);
ip = ((struct sockaddr_in *)&r->arp_pa)->sin_addr.s_addr;
if (r->arp_flags & ATF_PERM)
r->arp_flags |= ATF_COM;
if (!dev) {
struct rtable *rt = ip_route_output(net, ip, 0, 0, 0,
RT_SCOPE_LINK);
if (IS_ERR(rt))
return PTR_ERR(rt);
dev = rt->dst.dev;
ip_rt_put(rt);
if (!dev)
return -EINVAL;
}
switch (dev->type) { switch (dev->type) {
#if IS_ENABLED(CONFIG_FDDI) #if IS_ENABLED(CONFIG_FDDI)
case ARPHRD_FDDI: case ARPHRD_FDDI:
...@@ -1088,12 +1126,18 @@ static int arp_req_set(struct net *net, struct arpreq *r, ...@@ -1088,12 +1126,18 @@ static int arp_req_set(struct net *net, struct arpreq *r,
break; break;
} }
ip = ((struct sockaddr_in *)&r->arp_pa)->sin_addr.s_addr;
neigh = __neigh_lookup_errno(&arp_tbl, &ip, dev); neigh = __neigh_lookup_errno(&arp_tbl, &ip, dev);
err = PTR_ERR(neigh); err = PTR_ERR(neigh);
if (!IS_ERR(neigh)) { if (!IS_ERR(neigh)) {
unsigned int state = NUD_STALE; unsigned int state = NUD_STALE;
if (r->arp_flags & ATF_PERM)
if (r->arp_flags & ATF_PERM) {
r->arp_flags |= ATF_COM;
state = NUD_PERMANENT; state = NUD_PERMANENT;
}
err = neigh_update(neigh, (r->arp_flags & ATF_COM) ? err = neigh_update(neigh, (r->arp_flags & ATF_COM) ?
r->arp_ha.sa_data : NULL, state, r->arp_ha.sa_data : NULL, state,
NEIGH_UPDATE_F_OVERRIDE | NEIGH_UPDATE_F_OVERRIDE |
...@@ -1117,27 +1161,40 @@ static unsigned int arp_state_to_flags(struct neighbour *neigh) ...@@ -1117,27 +1161,40 @@ static unsigned int arp_state_to_flags(struct neighbour *neigh)
* Get an ARP cache entry. * Get an ARP cache entry.
*/ */
static int arp_req_get(struct arpreq *r, struct net_device *dev) static int arp_req_get(struct net *net, struct arpreq *r)
{ {
__be32 ip = ((struct sockaddr_in *) &r->arp_pa)->sin_addr.s_addr; __be32 ip = ((struct sockaddr_in *) &r->arp_pa)->sin_addr.s_addr;
struct neighbour *neigh; struct neighbour *neigh;
int err = -ENXIO; struct net_device *dev;
if (!r->arp_dev[0])
return -ENODEV;
dev = arp_req_dev_by_name(net, r, true);
if (IS_ERR(dev))
return PTR_ERR(dev);
neigh = neigh_lookup(&arp_tbl, &ip, dev); neigh = neigh_lookup(&arp_tbl, &ip, dev);
if (neigh) { if (!neigh)
if (!(READ_ONCE(neigh->nud_state) & NUD_NOARP)) { return -ENXIO;
if (READ_ONCE(neigh->nud_state) & NUD_NOARP) {
neigh_release(neigh);
return -ENXIO;
}
read_lock_bh(&neigh->lock); read_lock_bh(&neigh->lock);
memcpy(r->arp_ha.sa_data, neigh->ha, memcpy(r->arp_ha.sa_data, neigh->ha,
min(dev->addr_len, sizeof(r->arp_ha.sa_data_min))); min(dev->addr_len, sizeof(r->arp_ha.sa_data_min)));
r->arp_flags = arp_state_to_flags(neigh); r->arp_flags = arp_state_to_flags(neigh);
read_unlock_bh(&neigh->lock); read_unlock_bh(&neigh->lock);
r->arp_ha.sa_family = dev->type;
strscpy(r->arp_dev, dev->name, sizeof(r->arp_dev));
err = 0;
}
neigh_release(neigh); neigh_release(neigh);
}
return err; r->arp_ha.sa_family = dev->type;
netdev_copy_name(dev, r->arp_dev);
return 0;
} }
int arp_invalidate(struct net_device *dev, __be32 ip, bool force) int arp_invalidate(struct net_device *dev, __be32 ip, bool force)
...@@ -1168,37 +1225,31 @@ int arp_invalidate(struct net_device *dev, __be32 ip, bool force) ...@@ -1168,37 +1225,31 @@ int arp_invalidate(struct net_device *dev, __be32 ip, bool force)
static int arp_req_delete_public(struct net *net, struct arpreq *r, static int arp_req_delete_public(struct net *net, struct arpreq *r,
struct net_device *dev) struct net_device *dev)
{ {
__be32 ip = ((struct sockaddr_in *) &r->arp_pa)->sin_addr.s_addr;
__be32 mask = ((struct sockaddr_in *)&r->arp_netmask)->sin_addr.s_addr; __be32 mask = ((struct sockaddr_in *)&r->arp_netmask)->sin_addr.s_addr;
if (mask == htonl(0xFFFFFFFF)) if (mask) {
return pneigh_delete(&arp_tbl, net, &ip, dev); __be32 ip = ((struct sockaddr_in *)&r->arp_pa)->sin_addr.s_addr;
if (mask) return pneigh_delete(&arp_tbl, net, &ip, dev);
return -EINVAL; }
return arp_req_set_proxy(net, dev, 0); return arp_req_set_proxy(net, dev, 0);
} }
static int arp_req_delete(struct net *net, struct arpreq *r, static int arp_req_delete(struct net *net, struct arpreq *r)
struct net_device *dev)
{ {
struct net_device *dev;
__be32 ip; __be32 ip;
dev = arp_req_dev(net, r);
if (IS_ERR(dev))
return PTR_ERR(dev);
if (r->arp_flags & ATF_PUBL) if (r->arp_flags & ATF_PUBL)
return arp_req_delete_public(net, r, dev); return arp_req_delete_public(net, r, dev);
ip = ((struct sockaddr_in *)&r->arp_pa)->sin_addr.s_addr; ip = ((struct sockaddr_in *)&r->arp_pa)->sin_addr.s_addr;
if (!dev) {
struct rtable *rt = ip_route_output(net, ip, 0, 0, 0,
RT_SCOPE_LINK);
if (IS_ERR(rt))
return PTR_ERR(rt);
dev = rt->dst.dev;
ip_rt_put(rt);
if (!dev)
return -EINVAL;
}
return arp_invalidate(dev, ip, true); return arp_invalidate(dev, ip, true);
} }
...@@ -1208,9 +1259,9 @@ static int arp_req_delete(struct net *net, struct arpreq *r, ...@@ -1208,9 +1259,9 @@ static int arp_req_delete(struct net *net, struct arpreq *r,
int arp_ioctl(struct net *net, unsigned int cmd, void __user *arg) int arp_ioctl(struct net *net, unsigned int cmd, void __user *arg)
{ {
int err;
struct arpreq r; struct arpreq r;
struct net_device *dev = NULL; __be32 *netmask;
int err;
switch (cmd) { switch (cmd) {
case SIOCDARP: case SIOCDARP:
...@@ -1233,42 +1284,34 @@ int arp_ioctl(struct net *net, unsigned int cmd, void __user *arg) ...@@ -1233,42 +1284,34 @@ int arp_ioctl(struct net *net, unsigned int cmd, void __user *arg)
if (!(r.arp_flags & ATF_PUBL) && if (!(r.arp_flags & ATF_PUBL) &&
(r.arp_flags & (ATF_NETMASK | ATF_DONTPUB))) (r.arp_flags & (ATF_NETMASK | ATF_DONTPUB)))
return -EINVAL; return -EINVAL;
if (!(r.arp_flags & ATF_NETMASK))
((struct sockaddr_in *)&r.arp_netmask)->sin_addr.s_addr =
htonl(0xFFFFFFFFUL);
rtnl_lock();
if (r.arp_dev[0]) {
err = -ENODEV;
dev = __dev_get_by_name(net, r.arp_dev);
if (!dev)
goto out;
/* Mmmm... It is wrong... ARPHRD_NETROM==0 */ netmask = &((struct sockaddr_in *)&r.arp_netmask)->sin_addr.s_addr;
if (!r.arp_ha.sa_family) if (!(r.arp_flags & ATF_NETMASK))
r.arp_ha.sa_family = dev->type; *netmask = htonl(0xFFFFFFFFUL);
err = -EINVAL; else if (*netmask && *netmask != htonl(0xFFFFFFFFUL))
if ((r.arp_flags & ATF_COM) && r.arp_ha.sa_family != dev->type) return -EINVAL;
goto out;
} else if (cmd == SIOCGARP) {
err = -ENODEV;
goto out;
}
switch (cmd) { switch (cmd) {
case SIOCDARP: case SIOCDARP:
err = arp_req_delete(net, &r, dev); rtnl_lock();
err = arp_req_delete(net, &r);
rtnl_unlock();
break; break;
case SIOCSARP: case SIOCSARP:
err = arp_req_set(net, &r, dev); rtnl_lock();
err = arp_req_set(net, &r);
rtnl_unlock();
break; break;
case SIOCGARP: case SIOCGARP:
err = arp_req_get(&r, dev); rcu_read_lock();
err = arp_req_get(net, &r);
rcu_read_unlock();
if (!err && copy_to_user(arg, &r, sizeof(r)))
err = -EFAULT;
break; break;
} }
out:
rtnl_unlock();
if (cmd == SIOCGARP && !err && copy_to_user(arg, &r, sizeof(r)))
err = -EFAULT;
return err; return err;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment