dev_ioctl(): move copyin/copyout to callers
authorAl Viro <viro@zeniv.linux.org.uk>
Thu, 5 Oct 2017 16:59:44 +0000 (12:59 -0400)
committerAl Viro <viro@zeniv.linux.org.uk>
Thu, 25 Jan 2018 00:13:45 +0000 (19:13 -0500)
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
include/linux/netdevice.h
net/core/dev_ioctl.c
net/socket.c

index df5565d..24a62d5 100644 (file)
@@ -3315,7 +3315,8 @@ int netdev_rx_handler_register(struct net_device *dev,
 void netdev_rx_handler_unregister(struct net_device *dev);
 
 bool dev_valid_name(const char *name);
-int dev_ioctl(struct net *net, unsigned int cmd, void __user *);
+int dev_ioctl(struct net *net, unsigned int cmd, struct ifreq *ifr,
+               bool *need_copyout);
 int dev_ifconf(struct net *net, struct ifconf *, int);
 int dev_ethtool(struct net *net, struct ifreq *);
 unsigned int dev_get_flags(const struct net_device *);
index d262f15..0ab1af0 100644 (file)
  *     match.  --pb
  */
 
-static int dev_ifname(struct net *net, struct ifreq __user *arg)
+static int dev_ifname(struct net *net, struct ifreq *ifr)
 {
-       struct ifreq ifr;
-       int error;
-
-       /*
-        *      Fetch the caller's info block.
-        */
-
-       if (copy_from_user(&ifr, arg, sizeof(struct ifreq)))
-               return -EFAULT;
-       ifr.ifr_name[IFNAMSIZ-1] = 0;
-
-       error = netdev_get_name(net, ifr.ifr_name, ifr.ifr_ifindex);
-       if (error)
-               return error;
-
-       if (copy_to_user(arg, &ifr, sizeof(struct ifreq)))
-               return -EFAULT;
-       return 0;
+       ifr->ifr_name[IFNAMSIZ-1] = 0;
+       return netdev_get_name(net, ifr->ifr_name, ifr->ifr_ifindex);
 }
 
 static gifconf_func_t *gifconf_list[NPROTO];
@@ -402,24 +386,24 @@ EXPORT_SYMBOL(dev_load);
  *     positive or a negative errno code on error.
  */
 
-int dev_ioctl(struct net *net, unsigned int cmd, void __user *arg)
+int dev_ioctl(struct net *net, unsigned int cmd, struct ifreq *ifr, bool *need_copyout)
 {
-       struct ifreq ifr;
        int ret;
        char *colon;
 
+       if (need_copyout)
+               *need_copyout = true;
        if (cmd == SIOCGIFNAME)
-               return dev_ifname(net, (struct ifreq __user *)arg);
-
-       if (copy_from_user(&ifr, arg, sizeof(struct ifreq)))
-               return -EFAULT;
+               return dev_ifname(net, ifr);
 
-       ifr.ifr_name[IFNAMSIZ-1] = 0;
+       ifr->ifr_name[IFNAMSIZ-1] = 0;
 
-       colon = strchr(ifr.ifr_name, ':');
+       colon = strchr(ifr->ifr_name, ':');
        if (colon)
                *colon = 0;
 
+       dev_load(net, ifr->ifr_name);
+
        /*
         *      See which interface the caller is talking about.
         */
@@ -439,31 +423,19 @@ int dev_ioctl(struct net *net, unsigned int cmd, void __user *arg)
        case SIOCGIFMAP:
        case SIOCGIFINDEX:
        case SIOCGIFTXQLEN:
-               dev_load(net, ifr.ifr_name);
                rcu_read_lock();
-               ret = dev_ifsioc_locked(net, &ifr, cmd);
+               ret = dev_ifsioc_locked(net, ifr, cmd);
                rcu_read_unlock();
-               if (!ret) {
-                       if (colon)
-                               *colon = ':';
-                       if (copy_to_user(arg, &ifr,
-                                        sizeof(struct ifreq)))
-                               ret = -EFAULT;
-               }
+               if (colon)
+                       *colon = ':';
                return ret;
 
        case SIOCETHTOOL:
-               dev_load(net, ifr.ifr_name);
                rtnl_lock();
-               ret = dev_ethtool(net, &ifr);
+               ret = dev_ethtool(net, ifr);
                rtnl_unlock();
-               if (!ret) {
-                       if (colon)
-                               *colon = ':';
-                       if (copy_to_user(arg, &ifr,
-                                        sizeof(struct ifreq)))
-                               ret = -EFAULT;
-               }
+               if (colon)
+                       *colon = ':';
                return ret;
 
        /*
@@ -477,17 +449,11 @@ int dev_ioctl(struct net *net, unsigned int cmd, void __user *arg)
        case SIOCSIFNAME:
                if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
                        return -EPERM;
-               dev_load(net, ifr.ifr_name);
                rtnl_lock();
-               ret = dev_ifsioc(net, &ifr, cmd);
+               ret = dev_ifsioc(net, ifr, cmd);
                rtnl_unlock();
-               if (!ret) {
-                       if (colon)
-                               *colon = ':';
-                       if (copy_to_user(arg, &ifr,
-                                        sizeof(struct ifreq)))
-                               ret = -EFAULT;
-               }
+               if (colon)
+                       *colon = ':';
                return ret;
 
        /*
@@ -528,10 +494,11 @@ int dev_ioctl(struct net *net, unsigned int cmd, void __user *arg)
                /* fall through */
        case SIOCBONDSLAVEINFOQUERY:
        case SIOCBONDINFOQUERY:
-               dev_load(net, ifr.ifr_name);
                rtnl_lock();
-               ret = dev_ifsioc(net, &ifr, cmd);
+               ret = dev_ifsioc(net, ifr, cmd);
                rtnl_unlock();
+               if (need_copyout)
+                       *need_copyout = false;
                return ret;
 
        case SIOCGIFMEM:
@@ -551,13 +518,9 @@ int dev_ioctl(struct net *net, unsigned int cmd, void __user *arg)
                    cmd == SIOCGHWTSTAMP ||
                    (cmd >= SIOCDEVPRIVATE &&
                     cmd <= SIOCDEVPRIVATE + 15)) {
-                       dev_load(net, ifr.ifr_name);
                        rtnl_lock();
-                       ret = dev_ifsioc(net, &ifr, cmd);
+                       ret = dev_ifsioc(net, ifr, cmd);
                        rtnl_unlock();
-                       if (!ret && copy_to_user(arg, &ifr,
-                                                sizeof(struct ifreq)))
-                               ret = -EFAULT;
                        return ret;
                }
                return -ENOTTY;
index 1ad02d9..45d5155 100644 (file)
@@ -973,10 +973,17 @@ static long sock_do_ioctl(struct net *net, struct socket *sock,
                rtnl_unlock();
                if (!err && copy_to_user(argp, &ifc, sizeof(struct ifconf)))
                        err = -EFAULT;
-               return err;
+       } else {
+               struct ifreq ifr;
+               bool need_copyout;
+               if (copy_from_user(&ifr, argp, sizeof(struct ifreq)))
+                       return -EFAULT;
+               err = dev_ioctl(net, cmd, &ifr, &need_copyout);
+               if (!err && need_copyout)
+                       if (copy_to_user(argp, &ifr, sizeof(struct ifreq)))
+                               return -EFAULT;
        }
-
-       return dev_ioctl(net, cmd, argp);
+       return err;
 }
 
 /*
@@ -1000,8 +1007,15 @@ static long sock_ioctl(struct file *file, unsigned cmd, unsigned long arg)
        sock = file->private_data;
        sk = sock->sk;
        net = sock_net(sk);
-       if (cmd >= SIOCDEVPRIVATE && cmd <= (SIOCDEVPRIVATE + 15)) {
-               err = dev_ioctl(net, cmd, argp);
+       if (unlikely(cmd >= SIOCDEVPRIVATE && cmd <= (SIOCDEVPRIVATE + 15))) {
+               struct ifreq ifr;
+               bool need_copyout;
+               if (copy_from_user(&ifr, argp, sizeof(struct ifreq)))
+                       return -EFAULT;
+               err = dev_ioctl(net, cmd, &ifr, &need_copyout);
+               if (!err && need_copyout)
+                       if (copy_to_user(argp, &ifr, sizeof(struct ifreq)))
+                               return -EFAULT;
        } else
 #ifdef CONFIG_WEXT_CORE
        if (cmd >= SIOCIWFIRST && cmd <= SIOCIWLAST) {
@@ -2695,9 +2709,9 @@ static int ethtool_ioctl(struct net *net, struct compat_ifreq __user *ifr32)
 {
        struct compat_ethtool_rxnfc __user *compat_rxnfc;
        bool convert_in = false, convert_out = false;
-       size_t buf_size = ALIGN(sizeof(struct ifreq), 8);
-       struct ethtool_rxnfc __user *rxnfc;
-       struct ifreq __user *ifr;
+       size_t buf_size = 0;
+       struct ethtool_rxnfc __user *rxnfc = NULL;
+       struct ifreq ifr;
        u32 rule_cnt = 0, actual_rule_cnt;
        u32 ethcmd;
        u32 data;
@@ -2734,18 +2748,14 @@ static int ethtool_ioctl(struct net *net, struct compat_ifreq __user *ifr32)
        case ETHTOOL_SRXCLSRLDEL:
                buf_size += sizeof(struct ethtool_rxnfc);
                convert_in = true;
+               rxnfc = compat_alloc_user_space(buf_size);
                break;
        }
 
-       ifr = compat_alloc_user_space(buf_size);
-       rxnfc = (void __user *)ifr + ALIGN(sizeof(struct ifreq), 8);
-
-       if (copy_in_user(&ifr->ifr_name, &ifr32->ifr_name, IFNAMSIZ))
+       if (copy_from_user(&ifr.ifr_name, &ifr32->ifr_name, IFNAMSIZ))
                return -EFAULT;
 
-       if (put_user(convert_in ? rxnfc : compat_ptr(data),
-                    &ifr->ifr_ifru.ifru_data))
-               return -EFAULT;
+       ifr.ifr_data = convert_in ? rxnfc : (void __user *)compat_rxnfc;
 
        if (convert_in) {
                /* We expect there to be holes between fs.m_ext and
@@ -2773,7 +2783,7 @@ static int ethtool_ioctl(struct net *net, struct compat_ifreq __user *ifr32)
                        return -EFAULT;
        }
 
-       ret = dev_ioctl(net, SIOCETHTOOL, ifr);
+       ret = dev_ioctl(net, SIOCETHTOOL, &ifr, NULL);
        if (ret)
                return ret;
 
@@ -2814,50 +2824,43 @@ static int ethtool_ioctl(struct net *net, struct compat_ifreq __user *ifr32)
 
 static int compat_siocwandev(struct net *net, struct compat_ifreq __user *uifr32)
 {
-       void __user *uptr;
        compat_uptr_t uptr32;
-       struct ifreq __user *uifr;
+       struct ifreq ifr;
+       void __user *saved;
+       int err;
 
-       uifr = compat_alloc_user_space(sizeof(*uifr));
-       if (copy_in_user(uifr, uifr32, sizeof(struct compat_ifreq)))
+       if (copy_from_user(&ifr, uifr32, sizeof(struct compat_ifreq)))
                return -EFAULT;
 
        if (get_user(uptr32, &uifr32->ifr_settings.ifs_ifsu))
                return -EFAULT;
 
-       uptr = compat_ptr(uptr32);
-
-       if (put_user(uptr, &uifr->ifr_settings.ifs_ifsu.raw_hdlc))
-               return -EFAULT;
+       saved = ifr.ifr_settings.ifs_ifsu.raw_hdlc;
+       ifr.ifr_settings.ifs_ifsu.raw_hdlc = compat_ptr(uptr32);
 
-       return dev_ioctl(net, SIOCWANDEV, uifr);
+       err = dev_ioctl(net, SIOCWANDEV, &ifr, NULL);
+       if (!err) {
+               ifr.ifr_settings.ifs_ifsu.raw_hdlc = saved;
+               if (copy_to_user(uifr32, &ifr, sizeof(struct compat_ifreq)))
+                       err = -EFAULT;
+       }
+       return err;
 }
 
 /* Handle ioctls that use ifreq::ifr_data and just need struct ifreq converted */
 static int compat_ifr_data_ioctl(struct net *net, unsigned int cmd,
                                 struct compat_ifreq __user *u_ifreq32)
 {
-       struct ifreq __user *u_ifreq64;
-       char tmp_buf[IFNAMSIZ];
-       void __user *data64;
+       struct ifreq ifreq;
        u32 data32;
 
-       if (copy_from_user(&tmp_buf[0], &(u_ifreq32->ifr_ifrn.ifrn_name[0]),
-                          IFNAMSIZ))
+       if (copy_from_user(ifreq.ifr_name, u_ifreq32->ifr_name, IFNAMSIZ))
                return -EFAULT;
-       if (get_user(data32, &u_ifreq32->ifr_ifru.ifru_data))
+       if (get_user(data32, &u_ifreq32->ifr_data))
                return -EFAULT;
-       data64 = compat_ptr(data32);
+       ifreq.ifr_data = compat_ptr(data32);
 
-       u_ifreq64 = compat_alloc_user_space(sizeof(*u_ifreq64));
-
-       if (copy_to_user(&u_ifreq64->ifr_ifrn.ifrn_name[0], &tmp_buf[0],
-                        IFNAMSIZ))
-               return -EFAULT;
-       if (put_user(data64, &u_ifreq64->ifr_ifru.ifru_data))
-               return -EFAULT;
-
-       return dev_ioctl(net, cmd, u_ifreq64);
+       return dev_ioctl(net, cmd, &ifreq, NULL);
 }
 
 static int compat_sioc_ifmap(struct net *net, unsigned int cmd,
@@ -2865,7 +2868,6 @@ static int compat_sioc_ifmap(struct net *net, unsigned int cmd,
 {
        struct ifreq ifr;
        struct compat_ifmap __user *uifmap32;
-       mm_segment_t old_fs;
        int err;
 
        uifmap32 = &uifr32->ifr_ifru.ifru_map;
@@ -2879,10 +2881,7 @@ static int compat_sioc_ifmap(struct net *net, unsigned int cmd,
        if (err)
                return -EFAULT;
 
-       old_fs = get_fs();
-       set_fs(KERNEL_DS);
-       err = dev_ioctl(net, cmd, (void  __user __force *)&ifr);
-       set_fs(old_fs);
+       err = dev_ioctl(net, cmd, &ifr, NULL);
 
        if (cmd == SIOCGIFMAP && !err) {
                err = copy_to_user(uifr32, &ifr, sizeof(ifr.ifr_name));