diff options
author | Al Viro <viro@zeniv.linux.org.uk> | 2017-06-26 13:19:16 -0400 |
---|---|---|
committer | Al Viro <viro@zeniv.linux.org.uk> | 2018-01-24 19:13:45 -0500 |
commit | 36fd633ec98acd2028585c22128fcaa3da6d5770 (patch) | |
tree | 1fa77e8bf1662dea936b52107a0d39485110edb1 | |
parent | be1b6e8b5470e8311bfa1a3dfd7bd59e85a99759 (diff) |
net: separate SIOCGIFCONF handling from dev_ioctl()
Only two of dev_ioctl() callers may pass SIOCGIFCONF to it.
Separating that codepath from the rest of dev_ioctl() allows both
to simplify dev_ioctl() itself (all other cases work with struct ifreq *)
*and* seriously simplify the compat side of that beast: all it takes
is passing to inet_gifconf() an extra argument - the size of individual
records (sizeof(struct ifreq) or sizeof(struct compat_ifreq)). With
dev_ifconf() called directly from sock_do_ioctl()/compat_dev_ifconf()
that's easy to arrange.
As the result, compat side of SIOCGIFCONF doesn't need any
allocations, copy_in_user() back and forth, etc.
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
-rw-r--r-- | include/linux/netdevice.h | 4 | ||||
-rw-r--r-- | net/core/dev_ioctl.c | 29 | ||||
-rw-r--r-- | net/ipv4/devinet.c | 16 | ||||
-rw-r--r-- | net/socket.c | 79 |
4 files changed, 42 insertions, 86 deletions
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 581495f4e487..df5565d0369c 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h | |||
@@ -2761,7 +2761,8 @@ static inline bool dev_validate_header(const struct net_device *dev, | |||
2761 | return false; | 2761 | return false; |
2762 | } | 2762 | } |
2763 | 2763 | ||
2764 | typedef int gifconf_func_t(struct net_device * dev, char __user * bufptr, int len); | 2764 | typedef int gifconf_func_t(struct net_device * dev, char __user * bufptr, |
2765 | int len, int size); | ||
2765 | int register_gifconf(unsigned int family, gifconf_func_t *gifconf); | 2766 | int register_gifconf(unsigned int family, gifconf_func_t *gifconf); |
2766 | static inline int unregister_gifconf(unsigned int family) | 2767 | static inline int unregister_gifconf(unsigned int family) |
2767 | { | 2768 | { |
@@ -3315,6 +3316,7 @@ void netdev_rx_handler_unregister(struct net_device *dev); | |||
3315 | 3316 | ||
3316 | bool dev_valid_name(const char *name); | 3317 | bool dev_valid_name(const char *name); |
3317 | int dev_ioctl(struct net *net, unsigned int cmd, void __user *); | 3318 | int dev_ioctl(struct net *net, unsigned int cmd, void __user *); |
3319 | int dev_ifconf(struct net *net, struct ifconf *, int); | ||
3318 | int dev_ethtool(struct net *net, struct ifreq *); | 3320 | int dev_ethtool(struct net *net, struct ifreq *); |
3319 | unsigned int dev_get_flags(const struct net_device *); | 3321 | unsigned int dev_get_flags(const struct net_device *); |
3320 | int __dev_change_flags(struct net_device *, unsigned int flags); | 3322 | int __dev_change_flags(struct net_device *, unsigned int flags); |
diff --git a/net/core/dev_ioctl.c b/net/core/dev_ioctl.c index 7e690d0ccd05..5cdec23dd28e 100644 --- a/net/core/dev_ioctl.c +++ b/net/core/dev_ioctl.c | |||
@@ -66,9 +66,8 @@ EXPORT_SYMBOL(register_gifconf); | |||
66 | * Thus we will need a 'compatibility mode'. | 66 | * Thus we will need a 'compatibility mode'. |
67 | */ | 67 | */ |
68 | 68 | ||
69 | static int dev_ifconf(struct net *net, char __user *arg) | 69 | int dev_ifconf(struct net *net, struct ifconf *ifc, int size) |
70 | { | 70 | { |
71 | struct ifconf ifc; | ||
72 | struct net_device *dev; | 71 | struct net_device *dev; |
73 | char __user *pos; | 72 | char __user *pos; |
74 | int len; | 73 | int len; |
@@ -79,11 +78,8 @@ static int dev_ifconf(struct net *net, char __user *arg) | |||
79 | * Fetch the caller's info block. | 78 | * Fetch the caller's info block. |
80 | */ | 79 | */ |
81 | 80 | ||
82 | if (copy_from_user(&ifc, arg, sizeof(struct ifconf))) | 81 | pos = ifc->ifc_buf; |
83 | return -EFAULT; | 82 | len = ifc->ifc_len; |
84 | |||
85 | pos = ifc.ifc_buf; | ||
86 | len = ifc.ifc_len; | ||
87 | 83 | ||
88 | /* | 84 | /* |
89 | * Loop over the interfaces, and write an info block for each. | 85 | * Loop over the interfaces, and write an info block for each. |
@@ -95,10 +91,10 @@ static int dev_ifconf(struct net *net, char __user *arg) | |||
95 | if (gifconf_list[i]) { | 91 | if (gifconf_list[i]) { |
96 | int done; | 92 | int done; |
97 | if (!pos) | 93 | if (!pos) |
98 | done = gifconf_list[i](dev, NULL, 0); | 94 | done = gifconf_list[i](dev, NULL, 0, size); |
99 | else | 95 | else |
100 | done = gifconf_list[i](dev, pos + total, | 96 | done = gifconf_list[i](dev, pos + total, |
101 | len - total); | 97 | len - total, size); |
102 | if (done < 0) | 98 | if (done < 0) |
103 | return -EFAULT; | 99 | return -EFAULT; |
104 | total += done; | 100 | total += done; |
@@ -109,12 +105,12 @@ static int dev_ifconf(struct net *net, char __user *arg) | |||
109 | /* | 105 | /* |
110 | * All done. Write the updated control block back to the caller. | 106 | * All done. Write the updated control block back to the caller. |
111 | */ | 107 | */ |
112 | ifc.ifc_len = total; | 108 | ifc->ifc_len = total; |
113 | 109 | ||
114 | /* | 110 | /* |
115 | * Both BSD and Solaris return 0 here, so we do too. | 111 | * Both BSD and Solaris return 0 here, so we do too. |
116 | */ | 112 | */ |
117 | return copy_to_user(arg, &ifc, sizeof(struct ifconf)) ? -EFAULT : 0; | 113 | return 0; |
118 | } | 114 | } |
119 | 115 | ||
120 | /* | 116 | /* |
@@ -412,17 +408,6 @@ int dev_ioctl(struct net *net, unsigned int cmd, void __user *arg) | |||
412 | int ret; | 408 | int ret; |
413 | char *colon; | 409 | char *colon; |
414 | 410 | ||
415 | /* One special case: SIOCGIFCONF takes ifconf argument | ||
416 | and requires shared lock, because it sleeps writing | ||
417 | to user space. | ||
418 | */ | ||
419 | |||
420 | if (cmd == SIOCGIFCONF) { | ||
421 | rtnl_lock(); | ||
422 | ret = dev_ifconf(net, (char __user *) arg); | ||
423 | rtnl_unlock(); | ||
424 | return ret; | ||
425 | } | ||
426 | if (cmd == SIOCGIFNAME) | 411 | if (cmd == SIOCGIFNAME) |
427 | return dev_ifname(net, (struct ifreq __user *)arg); | 412 | return dev_ifname(net, (struct ifreq __user *)arg); |
428 | 413 | ||
diff --git a/net/ipv4/devinet.c b/net/ipv4/devinet.c index 7a93359fbc72..1771549d2438 100644 --- a/net/ipv4/devinet.c +++ b/net/ipv4/devinet.c | |||
@@ -1188,22 +1188,25 @@ rarok: | |||
1188 | goto out; | 1188 | goto out; |
1189 | } | 1189 | } |
1190 | 1190 | ||
1191 | static int inet_gifconf(struct net_device *dev, char __user *buf, int len) | 1191 | static int inet_gifconf(struct net_device *dev, char __user *buf, int len, int size) |
1192 | { | 1192 | { |
1193 | struct in_device *in_dev = __in_dev_get_rtnl(dev); | 1193 | struct in_device *in_dev = __in_dev_get_rtnl(dev); |
1194 | struct in_ifaddr *ifa; | 1194 | struct in_ifaddr *ifa; |
1195 | struct ifreq ifr; | 1195 | struct ifreq ifr; |
1196 | int done = 0; | 1196 | int done = 0; |
1197 | 1197 | ||
1198 | if (WARN_ON(size > sizeof(struct ifreq))) | ||
1199 | goto out; | ||
1200 | |||
1198 | if (!in_dev) | 1201 | if (!in_dev) |
1199 | goto out; | 1202 | goto out; |
1200 | 1203 | ||
1201 | for (ifa = in_dev->ifa_list; ifa; ifa = ifa->ifa_next) { | 1204 | for (ifa = in_dev->ifa_list; ifa; ifa = ifa->ifa_next) { |
1202 | if (!buf) { | 1205 | if (!buf) { |
1203 | done += sizeof(ifr); | 1206 | done += size; |
1204 | continue; | 1207 | continue; |
1205 | } | 1208 | } |
1206 | if (len < (int) sizeof(ifr)) | 1209 | if (len < size) |
1207 | break; | 1210 | break; |
1208 | memset(&ifr, 0, sizeof(struct ifreq)); | 1211 | memset(&ifr, 0, sizeof(struct ifreq)); |
1209 | strcpy(ifr.ifr_name, ifa->ifa_label); | 1212 | strcpy(ifr.ifr_name, ifa->ifa_label); |
@@ -1212,13 +1215,12 @@ static int inet_gifconf(struct net_device *dev, char __user *buf, int len) | |||
1212 | (*(struct sockaddr_in *)&ifr.ifr_addr).sin_addr.s_addr = | 1215 | (*(struct sockaddr_in *)&ifr.ifr_addr).sin_addr.s_addr = |
1213 | ifa->ifa_local; | 1216 | ifa->ifa_local; |
1214 | 1217 | ||
1215 | if (copy_to_user(buf, &ifr, sizeof(struct ifreq))) { | 1218 | if (copy_to_user(buf + done, &ifr, size)) { |
1216 | done = -EFAULT; | 1219 | done = -EFAULT; |
1217 | break; | 1220 | break; |
1218 | } | 1221 | } |
1219 | buf += sizeof(struct ifreq); | 1222 | len -= size; |
1220 | len -= sizeof(struct ifreq); | 1223 | done += size; |
1221 | done += sizeof(struct ifreq); | ||
1222 | } | 1224 | } |
1223 | out: | 1225 | out: |
1224 | return done; | 1226 | return done; |
diff --git a/net/socket.c b/net/socket.c index 1536515b6437..96e5b23a2a2e 100644 --- a/net/socket.c +++ b/net/socket.c | |||
@@ -961,10 +961,22 @@ static long sock_do_ioctl(struct net *net, struct socket *sock, | |||
961 | * If this ioctl is unknown try to hand it down | 961 | * If this ioctl is unknown try to hand it down |
962 | * to the NIC driver. | 962 | * to the NIC driver. |
963 | */ | 963 | */ |
964 | if (err == -ENOIOCTLCMD) | 964 | if (err != -ENOIOCTLCMD) |
965 | err = dev_ioctl(net, cmd, argp); | 965 | return err; |
966 | 966 | ||
967 | return err; | 967 | if (cmd == SIOCGIFCONF) { |
968 | struct ifconf ifc; | ||
969 | if (copy_from_user(&ifc, argp, sizeof(struct ifconf))) | ||
970 | return -EFAULT; | ||
971 | rtnl_lock(); | ||
972 | err = dev_ifconf(net, &ifc, sizeof(struct ifreq)); | ||
973 | rtnl_unlock(); | ||
974 | if (!err && copy_to_user(argp, &ifc, sizeof(struct ifconf))) | ||
975 | err = -EFAULT; | ||
976 | return err; | ||
977 | } | ||
978 | |||
979 | return dev_ioctl(net, cmd, argp); | ||
968 | } | 980 | } |
969 | 981 | ||
970 | /* | 982 | /* |
@@ -2673,70 +2685,25 @@ static int dev_ifname32(struct net *net, struct compat_ifreq __user *uifr32) | |||
2673 | return 0; | 2685 | return 0; |
2674 | } | 2686 | } |
2675 | 2687 | ||
2676 | static int dev_ifconf(struct net *net, struct compat_ifconf __user *uifc32) | 2688 | static int compat_dev_ifconf(struct net *net, struct compat_ifconf __user *uifc32) |
2677 | { | 2689 | { |
2678 | struct compat_ifconf ifc32; | 2690 | struct compat_ifconf ifc32; |
2679 | struct ifconf ifc; | 2691 | struct ifconf ifc; |
2680 | struct ifconf __user *uifc; | ||
2681 | struct compat_ifreq __user *ifr32; | ||
2682 | struct ifreq __user *ifr; | ||
2683 | unsigned int i, j; | ||
2684 | int err; | 2692 | int err; |
2685 | 2693 | ||
2686 | if (copy_from_user(&ifc32, uifc32, sizeof(struct compat_ifconf))) | 2694 | if (copy_from_user(&ifc32, uifc32, sizeof(struct compat_ifconf))) |
2687 | return -EFAULT; | 2695 | return -EFAULT; |
2688 | 2696 | ||
2689 | memset(&ifc, 0, sizeof(ifc)); | 2697 | ifc.ifc_len = ifc32.ifc_len; |
2690 | if (ifc32.ifcbuf == 0) { | 2698 | ifc.ifc_req = compat_ptr(ifc32.ifcbuf); |
2691 | ifc32.ifc_len = 0; | ||
2692 | ifc.ifc_len = 0; | ||
2693 | ifc.ifc_req = NULL; | ||
2694 | uifc = compat_alloc_user_space(sizeof(struct ifconf)); | ||
2695 | } else { | ||
2696 | size_t len = ((ifc32.ifc_len / sizeof(struct compat_ifreq)) + 1) * | ||
2697 | sizeof(struct ifreq); | ||
2698 | uifc = compat_alloc_user_space(sizeof(struct ifconf) + len); | ||
2699 | ifc.ifc_len = len; | ||
2700 | ifr = ifc.ifc_req = (void __user *)(uifc + 1); | ||
2701 | ifr32 = compat_ptr(ifc32.ifcbuf); | ||
2702 | for (i = 0; i < ifc32.ifc_len; i += sizeof(struct compat_ifreq)) { | ||
2703 | if (copy_in_user(ifr, ifr32, sizeof(struct compat_ifreq))) | ||
2704 | return -EFAULT; | ||
2705 | ifr++; | ||
2706 | ifr32++; | ||
2707 | } | ||
2708 | } | ||
2709 | if (copy_to_user(uifc, &ifc, sizeof(struct ifconf))) | ||
2710 | return -EFAULT; | ||
2711 | 2699 | ||
2712 | err = dev_ioctl(net, SIOCGIFCONF, uifc); | 2700 | rtnl_lock(); |
2701 | err = dev_ifconf(net, &ifc, sizeof(struct compat_ifreq)); | ||
2702 | rtnl_unlock(); | ||
2713 | if (err) | 2703 | if (err) |
2714 | return err; | 2704 | return err; |
2715 | 2705 | ||
2716 | if (copy_from_user(&ifc, uifc, sizeof(struct ifconf))) | 2706 | ifc32.ifc_len = ifc.ifc_len; |
2717 | return -EFAULT; | ||
2718 | |||
2719 | ifr = ifc.ifc_req; | ||
2720 | ifr32 = compat_ptr(ifc32.ifcbuf); | ||
2721 | for (i = 0, j = 0; | ||
2722 | i + sizeof(struct compat_ifreq) <= ifc32.ifc_len && j < ifc.ifc_len; | ||
2723 | i += sizeof(struct compat_ifreq), j += sizeof(struct ifreq)) { | ||
2724 | if (copy_in_user(ifr32, ifr, sizeof(struct compat_ifreq))) | ||
2725 | return -EFAULT; | ||
2726 | ifr32++; | ||
2727 | ifr++; | ||
2728 | } | ||
2729 | |||
2730 | if (ifc32.ifcbuf == 0) { | ||
2731 | /* Translate from 64-bit structure multiple to | ||
2732 | * a 32-bit one. | ||
2733 | */ | ||
2734 | i = ifc.ifc_len; | ||
2735 | i = ((i / sizeof(struct ifreq)) * sizeof(struct compat_ifreq)); | ||
2736 | ifc32.ifc_len = i; | ||
2737 | } else { | ||
2738 | ifc32.ifc_len = i; | ||
2739 | } | ||
2740 | if (copy_to_user(uifc32, &ifc32, sizeof(struct compat_ifconf))) | 2707 | if (copy_to_user(uifc32, &ifc32, sizeof(struct compat_ifconf))) |
2741 | return -EFAULT; | 2708 | return -EFAULT; |
2742 | 2709 | ||
@@ -3133,7 +3100,7 @@ static int compat_sock_ioctl_trans(struct file *file, struct socket *sock, | |||
3133 | case SIOCGIFNAME: | 3100 | case SIOCGIFNAME: |
3134 | return dev_ifname32(net, argp); | 3101 | return dev_ifname32(net, argp); |
3135 | case SIOCGIFCONF: | 3102 | case SIOCGIFCONF: |
3136 | return dev_ifconf(net, argp); | 3103 | return compat_dev_ifconf(net, argp); |
3137 | case SIOCETHTOOL: | 3104 | case SIOCETHTOOL: |
3138 | return ethtool_ioctl(net, argp); | 3105 | return ethtool_ioctl(net, argp); |
3139 | case SIOCWANDEV: | 3106 | case SIOCWANDEV: |