* the information ethtool needs.
  */
 
+#include <linux/compat.h>
 #include <linux/module.h>
 #include <linux/types.h>
 #include <linux/capability.h>
        return ret;
 }
 
+static noinline_for_stack int
+ethtool_rxnfc_copy_from_compat(struct ethtool_rxnfc *rxnfc,
+                              const struct compat_ethtool_rxnfc __user *useraddr,
+                              size_t size)
+{
+       struct compat_ethtool_rxnfc crxnfc = {};
+
+       /* We expect there to be holes between fs.m_ext and
+        * fs.ring_cookie and at the end of fs, but nowhere else.
+        * On non-x86, no conversion should be needed.
+        */
+       BUILD_BUG_ON(!IS_ENABLED(CONFIG_X86_64) &&
+                    sizeof(struct compat_ethtool_rxnfc) !=
+                    sizeof(struct ethtool_rxnfc));
+       BUILD_BUG_ON(offsetof(struct compat_ethtool_rxnfc, fs.m_ext) +
+                    sizeof(useraddr->fs.m_ext) !=
+                    offsetof(struct ethtool_rxnfc, fs.m_ext) +
+                    sizeof(rxnfc->fs.m_ext));
+       BUILD_BUG_ON(offsetof(struct compat_ethtool_rxnfc, fs.location) -
+                    offsetof(struct compat_ethtool_rxnfc, fs.ring_cookie) !=
+                    offsetof(struct ethtool_rxnfc, fs.location) -
+                    offsetof(struct ethtool_rxnfc, fs.ring_cookie));
+
+       if (copy_from_user(&crxnfc, useraddr, min(size, sizeof(crxnfc))))
+               return -EFAULT;
+
+       *rxnfc = (struct ethtool_rxnfc) {
+               .cmd            = crxnfc.cmd,
+               .flow_type      = crxnfc.flow_type,
+               .data           = crxnfc.data,
+               .fs             = {
+                       .flow_type      = crxnfc.fs.flow_type,
+                       .h_u            = crxnfc.fs.h_u,
+                       .h_ext          = crxnfc.fs.h_ext,
+                       .m_u            = crxnfc.fs.m_u,
+                       .m_ext          = crxnfc.fs.m_ext,
+                       .ring_cookie    = crxnfc.fs.ring_cookie,
+                       .location       = crxnfc.fs.location,
+               },
+               .rule_cnt       = crxnfc.rule_cnt,
+       };
+
+       return 0;
+}
+
+static int ethtool_rxnfc_copy_from_user(struct ethtool_rxnfc *rxnfc,
+                                       const void __user *useraddr,
+                                       size_t size)
+{
+       if (compat_need_64bit_alignment_fixup())
+               return ethtool_rxnfc_copy_from_compat(rxnfc, useraddr, size);
+
+       if (copy_from_user(rxnfc, useraddr, size))
+               return -EFAULT;
+
+       return 0;
+}
+
+static int ethtool_rxnfc_copy_to_compat(void __user *useraddr,
+                                       const struct ethtool_rxnfc *rxnfc,
+                                       size_t size, const u32 *rule_buf)
+{
+       struct compat_ethtool_rxnfc crxnfc;
+
+       memset(&crxnfc, 0, sizeof(crxnfc));
+       crxnfc = (struct compat_ethtool_rxnfc) {
+               .cmd            = rxnfc->cmd,
+               .flow_type      = rxnfc->flow_type,
+               .data           = rxnfc->data,
+               .fs             = {
+                       .flow_type      = rxnfc->fs.flow_type,
+                       .h_u            = rxnfc->fs.h_u,
+                       .h_ext          = rxnfc->fs.h_ext,
+                       .m_u            = rxnfc->fs.m_u,
+                       .m_ext          = rxnfc->fs.m_ext,
+                       .ring_cookie    = rxnfc->fs.ring_cookie,
+                       .location       = rxnfc->fs.location,
+               },
+               .rule_cnt       = rxnfc->rule_cnt,
+       };
+
+       if (copy_to_user(useraddr, &crxnfc, min(size, sizeof(crxnfc))))
+               return -EFAULT;
+
+       return 0;
+}
+
+static int ethtool_rxnfc_copy_to_user(void __user *useraddr,
+                                     const struct ethtool_rxnfc *rxnfc,
+                                     size_t size, const u32 *rule_buf)
+{
+       int ret;
+
+       if (compat_need_64bit_alignment_fixup()) {
+               ret = ethtool_rxnfc_copy_to_compat(useraddr, rxnfc, size,
+                                                  rule_buf);
+               useraddr += offsetof(struct compat_ethtool_rxnfc, rule_locs);
+       } else {
+               ret = copy_to_user(useraddr, &rxnfc, size);
+               useraddr += offsetof(struct ethtool_rxnfc, rule_locs);
+       }
+
+       if (ret)
+               return -EFAULT;
+
+       if (rule_buf) {
+               if (copy_to_user(useraddr, rule_buf,
+                                rxnfc->rule_cnt * sizeof(u32)))
+                       return -EFAULT;
+       }
+
+       return 0;
+}
+
 static noinline_for_stack int ethtool_set_rxnfc(struct net_device *dev,
                                                u32 cmd, void __user *useraddr)
 {
                info_size = (offsetof(struct ethtool_rxnfc, data) +
                             sizeof(info.data));
 
-       if (copy_from_user(&info, useraddr, info_size))
+       if (ethtool_rxnfc_copy_from_user(&info, useraddr, info_size))
                return -EFAULT;
 
        rc = dev->ethtool_ops->set_rxnfc(dev, &info);
                return rc;
 
        if (cmd == ETHTOOL_SRXCLSRLINS &&
-           copy_to_user(useraddr, &info, info_size))
+           ethtool_rxnfc_copy_to_user(useraddr, &info, info_size, NULL))
                return -EFAULT;
 
        return 0;
                info_size = (offsetof(struct ethtool_rxnfc, data) +
                             sizeof(info.data));
 
-       if (copy_from_user(&info, useraddr, info_size))
+       if (ethtool_rxnfc_copy_from_user(&info, useraddr, info_size))
                return -EFAULT;
 
        /* If FLOW_RSS was requested then user-space must be using the
         */
        if (cmd == ETHTOOL_GRXFH && info.flow_type & FLOW_RSS) {
                info_size = sizeof(info);
-               if (copy_from_user(&info, useraddr, info_size))
+               if (ethtool_rxnfc_copy_from_user(&info, useraddr, info_size))
                        return -EFAULT;
                /* Since malicious users may modify the original data,
                 * we need to check whether FLOW_RSS is still requested.
        if (ret < 0)
                goto err_out;
 
-       ret = -EFAULT;
-       if (copy_to_user(useraddr, &info, info_size))
-               goto err_out;
-
-       if (rule_buf) {
-               useraddr += offsetof(struct ethtool_rxnfc, rule_locs);
-               if (copy_to_user(useraddr, rule_buf,
-                                info.rule_cnt * sizeof(u32)))
-                       goto err_out;
-       }
-       ret = 0;
-
+       ret = ethtool_rxnfc_copy_to_user(useraddr, &info, info_size, rule_buf);
 err_out:
        kfree(rule_buf);
 
 
        return 0;
 }
 
-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 = 0;
-       struct ethtool_rxnfc __user *rxnfc = NULL;
-       struct ifreq ifr;
-       u32 rule_cnt = 0, actual_rule_cnt;
-       u32 ethcmd;
-       u32 data;
-       int ret;
-
-       if (get_user(data, &ifr32->ifr_ifru.ifru_data))
-               return -EFAULT;
-
-       compat_rxnfc = compat_ptr(data);
-
-       if (get_user(ethcmd, &compat_rxnfc->cmd))
-               return -EFAULT;
-
-       /* Most ethtool structures are defined without padding.
-        * Unfortunately struct ethtool_rxnfc is an exception.
-        */
-       switch (ethcmd) {
-       default:
-               break;
-       case ETHTOOL_GRXCLSRLALL:
-               /* Buffer size is variable */
-               if (get_user(rule_cnt, &compat_rxnfc->rule_cnt))
-                       return -EFAULT;
-               if (rule_cnt > KMALLOC_MAX_SIZE / sizeof(u32))
-                       return -ENOMEM;
-               buf_size += rule_cnt * sizeof(u32);
-               fallthrough;
-       case ETHTOOL_GRXRINGS:
-       case ETHTOOL_GRXCLSRLCNT:
-       case ETHTOOL_GRXCLSRULE:
-       case ETHTOOL_SRXCLSRLINS:
-               convert_out = true;
-               fallthrough;
-       case ETHTOOL_SRXCLSRLDEL:
-               buf_size += sizeof(struct ethtool_rxnfc);
-               convert_in = true;
-               rxnfc = compat_alloc_user_space(buf_size);
-               break;
-       }
-
-       if (copy_from_user(&ifr.ifr_name, &ifr32->ifr_name, IFNAMSIZ))
-               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
-                * fs.ring_cookie and at the end of fs, but nowhere else.
-                */
-               BUILD_BUG_ON(offsetof(struct compat_ethtool_rxnfc, fs.m_ext) +
-                            sizeof(compat_rxnfc->fs.m_ext) !=
-                            offsetof(struct ethtool_rxnfc, fs.m_ext) +
-                            sizeof(rxnfc->fs.m_ext));
-               BUILD_BUG_ON(
-                       offsetof(struct compat_ethtool_rxnfc, fs.location) -
-                       offsetof(struct compat_ethtool_rxnfc, fs.ring_cookie) !=
-                       offsetof(struct ethtool_rxnfc, fs.location) -
-                       offsetof(struct ethtool_rxnfc, fs.ring_cookie));
-
-               if (copy_in_user(rxnfc, compat_rxnfc,
-                                (void __user *)(&rxnfc->fs.m_ext + 1) -
-                                (void __user *)rxnfc) ||
-                   copy_in_user(&rxnfc->fs.ring_cookie,
-                                &compat_rxnfc->fs.ring_cookie,
-                                (void __user *)(&rxnfc->fs.location + 1) -
-                                (void __user *)&rxnfc->fs.ring_cookie))
-                       return -EFAULT;
-               if (ethcmd == ETHTOOL_GRXCLSRLALL) {
-                       if (put_user(rule_cnt, &rxnfc->rule_cnt))
-                               return -EFAULT;
-               } else if (copy_in_user(&rxnfc->rule_cnt,
-                                       &compat_rxnfc->rule_cnt,
-                                       sizeof(rxnfc->rule_cnt)))
-                       return -EFAULT;
-       }
-
-       ret = dev_ioctl(net, SIOCETHTOOL, &ifr, NULL);
-       if (ret)
-               return ret;
-
-       if (convert_out) {
-               if (copy_in_user(compat_rxnfc, rxnfc,
-                                (const void __user *)(&rxnfc->fs.m_ext + 1) -
-                                (const void __user *)rxnfc) ||
-                   copy_in_user(&compat_rxnfc->fs.ring_cookie,
-                                &rxnfc->fs.ring_cookie,
-                                (const void __user *)(&rxnfc->fs.location + 1) -
-                                (const void __user *)&rxnfc->fs.ring_cookie) ||
-                   copy_in_user(&compat_rxnfc->rule_cnt, &rxnfc->rule_cnt,
-                                sizeof(rxnfc->rule_cnt)))
-                       return -EFAULT;
-
-               if (ethcmd == ETHTOOL_GRXCLSRLALL) {
-                       /* As an optimisation, we only copy the actual
-                        * number of rules that the underlying
-                        * function returned.  Since Mallory might
-                        * change the rule count in user memory, we
-                        * check that it is less than the rule count
-                        * originally given (as the user buffer size),
-                        * which has been range-checked.
-                        */
-                       if (get_user(actual_rule_cnt, &rxnfc->rule_cnt))
-                               return -EFAULT;
-                       if (actual_rule_cnt < rule_cnt)
-                               rule_cnt = actual_rule_cnt;
-                       if (copy_in_user(&compat_rxnfc->rule_locs[0],
-                                        &rxnfc->rule_locs[0],
-                                        rule_cnt * sizeof(u32)))
-                               return -EFAULT;
-               }
-       }
-
-       return 0;
-}
-
 static int compat_siocwandev(struct net *net, struct compat_ifreq __user *uifr32)
 {
        compat_uptr_t uptr32;
                return old_bridge_ioctl(argp);
        case SIOCGIFCONF:
                return compat_dev_ifconf(net, argp);
-       case SIOCETHTOOL:
-               return ethtool_ioctl(net, argp);
        case SIOCWANDEV:
                return compat_siocwandev(net, argp);
        case SIOCGIFMAP:
                return sock->ops->gettstamp(sock, argp, cmd == SIOCGSTAMP_OLD,
                                            !COMPAT_USE_64BIT_TIME);
 
+       case SIOCETHTOOL:
        case SIOCBONDSLAVEINFOQUERY:
        case SIOCBONDINFOQUERY:
        case SIOCSHWTSTAMP: