Re: [PATCH 2/2] dev_ioctl: split out SIOC?IFMAP ioctls

From: Christoph Hellwig
Date: Sat Sep 19 2020 - 01:48:48 EST


> diff --git a/include/uapi/linux/if.h b/include/uapi/linux/if.h
> index 797ba2c1562a..a332d6ae4dc6 100644
> --- a/include/uapi/linux/if.h
> +++ b/include/uapi/linux/if.h
> @@ -247,7 +247,13 @@ struct ifreq {
> short ifru_flags;
> int ifru_ivalue;
> int ifru_mtu;
> +#ifndef __KERNEL__
> + /*
> + * ifru_map is rarely used but causes the incompatibility
> + * between native and compat mode.
> + */
> struct ifmap ifru_map;
> +#endif

Do we need a way to verify that this never changes the struct size?

> +int dev_ifmap(struct net *net, struct ifreq __user *ifr, unsigned int cmd)
> +{
> + struct net_device *dev;
> + char ifname[IFNAMSIZ];
> + char *colon;
> + struct compat_ifmap cifmap;
> + struct ifmap ifmap;
> + int ret;
> +
> + if (copy_from_user(ifname, ifr->ifr_name, sizeof(ifname)))
> + return -EFAULT;
> + ifname[IFNAMSIZ-1] = 0;
> + colon = strchr(ifname, ':');
> + if (colon)
> + *colon = 0;
> + dev_load(net, ifname);
> +
> + switch (cmd) {
> + case SIOCGIFMAP:
> + rcu_read_lock();
> + dev = dev_get_by_name_rcu(net, ifname);
> + if (!dev) {
> + rcu_read_unlock();
> + return -ENODEV;
> + }
> +
> + if (in_compat_syscall()) {
> + cifmap.mem_start = dev->mem_start;
> + cifmap.mem_end = dev->mem_end;
> + cifmap.base_addr = dev->base_addr;
> + cifmap.irq = dev->irq;
> + cifmap.dma = dev->dma;
> + cifmap.port = dev->if_port;
> + rcu_read_unlock();
> +
> + ret = copy_to_user(&ifr->ifr_data,
> + &cifmap, sizeof(cifmap));
> + } else {
> + ifmap.mem_start = dev->mem_start;
> + ifmap.mem_end = dev->mem_end;
> + ifmap.base_addr = dev->base_addr;
> + ifmap.irq = dev->irq;
> + ifmap.dma = dev->dma;
> + ifmap.port = dev->if_port;
> + rcu_read_unlock();
> +
> + ret = copy_to_user(&ifr->ifr_data,
> + &ifmap, sizeof(ifmap));
> + }
> + ret = ret ? -EFAULT : 0;
> + break;
> +
> + case SIOCSIFMAP:
> + if (!capable(CAP_NET_ADMIN) ||
> + !ns_capable(net->user_ns, CAP_NET_ADMIN))
> + return -EPERM;
> +
> + if (in_compat_syscall()) {
> + if (copy_from_user(&cifmap, &ifr->ifr_data,
> + sizeof(cifmap)))
> + return -EFAULT;
> +
> + ifmap.mem_start = cifmap.mem_start;
> + ifmap.mem_end = cifmap.mem_end;
> + ifmap.base_addr = cifmap.base_addr;
> + ifmap.irq = cifmap.irq;
> + ifmap.dma = cifmap.dma;
> + ifmap.port = cifmap.port;
> + } else {
> + if (copy_from_user(&ifmap, &ifr->ifr_data,
> + sizeof(ifmap)))
> + return -EFAULT;
> + }
> +
> + rtnl_lock();
> + dev = __dev_get_by_name(net, ifname);
> + if (!dev || !netif_device_present(dev))
> + ret = -ENODEV;
> + else if (!dev->netdev_ops->ndo_set_config)
> + ret = -EOPNOTSUPP;
> + else
> + ret = dev->netdev_ops->ndo_set_config(dev, &ifmap);
> + rtnl_unlock();
> + break;
> + }
> + return ret;

I'd rather split this into a separate hepers for each ioctl command
instead of having anothr multiplexer here, maybe with another helper
for the common code.

I also find the rcu unlock inside the branches rather strange, but
I can't think of a good alternative.