Skip to content

Commit dd98d28

Browse files
arndbdavem330
authored andcommitted
ethtool: improve compat ioctl handling
The ethtool compat ioctl handling is hidden away in net/socket.c, which introduces a couple of minor oddities: - The implementation may end up diverging, as seen in the RXNFC extension in commit 84a1d9c ("net: ethtool: extend RXNFC API to support RSS spreading of filter matches") that does not work in compat mode. - Most architectures do not need the compat handling at all because u64 and compat_u64 have the same alignment. - On x86, the conversion is done for both x32 and i386 user space, but it's actually wrong to do it for x32 and cannot work there. - On 32-bit Arm, it never worked for compat oabi user space, since that needs to do the same conversion but does not. - It would be nice to get rid of both compat_alloc_user_space() and copy_in_user() throughout the kernel. None of these actually seems to be a serious problem that real users are likely to encounter, but fixing all of them actually leads to code that is both shorter and more readable. Signed-off-by: Arnd Bergmann <arnd@arndb.de> Reviewed-by: Christoph Hellwig <hch@lst.de> Signed-off-by: David S. Miller <davem@davemloft.net>
1 parent 1a33b18 commit dd98d28

File tree

3 files changed

+121
-144
lines changed

3 files changed

+121
-144
lines changed

include/linux/ethtool.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
#include <linux/compat.h>
1818
#include <uapi/linux/ethtool.h>
1919

20-
#ifdef CONFIG_COMPAT
21-
2220
struct compat_ethtool_rx_flow_spec {
2321
u32 flow_type;
2422
union ethtool_flow_union h_u;
@@ -38,8 +36,6 @@ struct compat_ethtool_rxnfc {
3836
u32 rule_locs[];
3937
};
4038

41-
#endif /* CONFIG_COMPAT */
42-
4339
#include <linux/rculist.h>
4440

4541
/**

net/ethtool/ioctl.c

Lines changed: 120 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* the information ethtool needs.
88
*/
99

10+
#include <linux/compat.h>
1011
#include <linux/module.h>
1112
#include <linux/types.h>
1213
#include <linux/capability.h>
@@ -807,6 +808,120 @@ static noinline_for_stack int ethtool_get_sset_info(struct net_device *dev,
807808
return ret;
808809
}
809810

811+
static noinline_for_stack int
812+
ethtool_rxnfc_copy_from_compat(struct ethtool_rxnfc *rxnfc,
813+
const struct compat_ethtool_rxnfc __user *useraddr,
814+
size_t size)
815+
{
816+
struct compat_ethtool_rxnfc crxnfc = {};
817+
818+
/* We expect there to be holes between fs.m_ext and
819+
* fs.ring_cookie and at the end of fs, but nowhere else.
820+
* On non-x86, no conversion should be needed.
821+
*/
822+
BUILD_BUG_ON(!IS_ENABLED(CONFIG_X86_64) &&
823+
sizeof(struct compat_ethtool_rxnfc) !=
824+
sizeof(struct ethtool_rxnfc));
825+
BUILD_BUG_ON(offsetof(struct compat_ethtool_rxnfc, fs.m_ext) +
826+
sizeof(useraddr->fs.m_ext) !=
827+
offsetof(struct ethtool_rxnfc, fs.m_ext) +
828+
sizeof(rxnfc->fs.m_ext));
829+
BUILD_BUG_ON(offsetof(struct compat_ethtool_rxnfc, fs.location) -
830+
offsetof(struct compat_ethtool_rxnfc, fs.ring_cookie) !=
831+
offsetof(struct ethtool_rxnfc, fs.location) -
832+
offsetof(struct ethtool_rxnfc, fs.ring_cookie));
833+
834+
if (copy_from_user(&crxnfc, useraddr, min(size, sizeof(crxnfc))))
835+
return -EFAULT;
836+
837+
*rxnfc = (struct ethtool_rxnfc) {
838+
.cmd = crxnfc.cmd,
839+
.flow_type = crxnfc.flow_type,
840+
.data = crxnfc.data,
841+
.fs = {
842+
.flow_type = crxnfc.fs.flow_type,
843+
.h_u = crxnfc.fs.h_u,
844+
.h_ext = crxnfc.fs.h_ext,
845+
.m_u = crxnfc.fs.m_u,
846+
.m_ext = crxnfc.fs.m_ext,
847+
.ring_cookie = crxnfc.fs.ring_cookie,
848+
.location = crxnfc.fs.location,
849+
},
850+
.rule_cnt = crxnfc.rule_cnt,
851+
};
852+
853+
return 0;
854+
}
855+
856+
static int ethtool_rxnfc_copy_from_user(struct ethtool_rxnfc *rxnfc,
857+
const void __user *useraddr,
858+
size_t size)
859+
{
860+
if (compat_need_64bit_alignment_fixup())
861+
return ethtool_rxnfc_copy_from_compat(rxnfc, useraddr, size);
862+
863+
if (copy_from_user(rxnfc, useraddr, size))
864+
return -EFAULT;
865+
866+
return 0;
867+
}
868+
869+
static int ethtool_rxnfc_copy_to_compat(void __user *useraddr,
870+
const struct ethtool_rxnfc *rxnfc,
871+
size_t size, const u32 *rule_buf)
872+
{
873+
struct compat_ethtool_rxnfc crxnfc;
874+
875+
memset(&crxnfc, 0, sizeof(crxnfc));
876+
crxnfc = (struct compat_ethtool_rxnfc) {
877+
.cmd = rxnfc->cmd,
878+
.flow_type = rxnfc->flow_type,
879+
.data = rxnfc->data,
880+
.fs = {
881+
.flow_type = rxnfc->fs.flow_type,
882+
.h_u = rxnfc->fs.h_u,
883+
.h_ext = rxnfc->fs.h_ext,
884+
.m_u = rxnfc->fs.m_u,
885+
.m_ext = rxnfc->fs.m_ext,
886+
.ring_cookie = rxnfc->fs.ring_cookie,
887+
.location = rxnfc->fs.location,
888+
},
889+
.rule_cnt = rxnfc->rule_cnt,
890+
};
891+
892+
if (copy_to_user(useraddr, &crxnfc, min(size, sizeof(crxnfc))))
893+
return -EFAULT;
894+
895+
return 0;
896+
}
897+
898+
static int ethtool_rxnfc_copy_to_user(void __user *useraddr,
899+
const struct ethtool_rxnfc *rxnfc,
900+
size_t size, const u32 *rule_buf)
901+
{
902+
int ret;
903+
904+
if (compat_need_64bit_alignment_fixup()) {
905+
ret = ethtool_rxnfc_copy_to_compat(useraddr, rxnfc, size,
906+
rule_buf);
907+
useraddr += offsetof(struct compat_ethtool_rxnfc, rule_locs);
908+
} else {
909+
ret = copy_to_user(useraddr, &rxnfc, size);
910+
useraddr += offsetof(struct ethtool_rxnfc, rule_locs);
911+
}
912+
913+
if (ret)
914+
return -EFAULT;
915+
916+
if (rule_buf) {
917+
if (copy_to_user(useraddr, rule_buf,
918+
rxnfc->rule_cnt * sizeof(u32)))
919+
return -EFAULT;
920+
}
921+
922+
return 0;
923+
}
924+
810925
static noinline_for_stack int ethtool_set_rxnfc(struct net_device *dev,
811926
u32 cmd, void __user *useraddr)
812927
{
@@ -825,15 +940,15 @@ static noinline_for_stack int ethtool_set_rxnfc(struct net_device *dev,
825940
info_size = (offsetof(struct ethtool_rxnfc, data) +
826941
sizeof(info.data));
827942

828-
if (copy_from_user(&info, useraddr, info_size))
943+
if (ethtool_rxnfc_copy_from_user(&info, useraddr, info_size))
829944
return -EFAULT;
830945

831946
rc = dev->ethtool_ops->set_rxnfc(dev, &info);
832947
if (rc)
833948
return rc;
834949

835950
if (cmd == ETHTOOL_SRXCLSRLINS &&
836-
copy_to_user(useraddr, &info, info_size))
951+
ethtool_rxnfc_copy_to_user(useraddr, &info, info_size, NULL))
837952
return -EFAULT;
838953

839954
return 0;
@@ -859,15 +974,15 @@ static noinline_for_stack int ethtool_get_rxnfc(struct net_device *dev,
859974
info_size = (offsetof(struct ethtool_rxnfc, data) +
860975
sizeof(info.data));
861976

862-
if (copy_from_user(&info, useraddr, info_size))
977+
if (ethtool_rxnfc_copy_from_user(&info, useraddr, info_size))
863978
return -EFAULT;
864979

865980
/* If FLOW_RSS was requested then user-space must be using the
866981
* new definition, as FLOW_RSS is newer.
867982
*/
868983
if (cmd == ETHTOOL_GRXFH && info.flow_type & FLOW_RSS) {
869984
info_size = sizeof(info);
870-
if (copy_from_user(&info, useraddr, info_size))
985+
if (ethtool_rxnfc_copy_from_user(&info, useraddr, info_size))
871986
return -EFAULT;
872987
/* Since malicious users may modify the original data,
873988
* we need to check whether FLOW_RSS is still requested.
@@ -893,18 +1008,7 @@ static noinline_for_stack int ethtool_get_rxnfc(struct net_device *dev,
8931008
if (ret < 0)
8941009
goto err_out;
8951010

896-
ret = -EFAULT;
897-
if (copy_to_user(useraddr, &info, info_size))
898-
goto err_out;
899-
900-
if (rule_buf) {
901-
useraddr += offsetof(struct ethtool_rxnfc, rule_locs);
902-
if (copy_to_user(useraddr, rule_buf,
903-
info.rule_cnt * sizeof(u32)))
904-
goto err_out;
905-
}
906-
ret = 0;
907-
1011+
ret = ethtool_rxnfc_copy_to_user(useraddr, &info, info_size, rule_buf);
9081012
err_out:
9091013
kfree(rule_buf);
9101014

net/socket.c

Lines changed: 1 addition & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -3152,128 +3152,6 @@ static int compat_dev_ifconf(struct net *net, struct compat_ifconf __user *uifc3
31523152
return 0;
31533153
}
31543154

3155-
static int ethtool_ioctl(struct net *net, struct compat_ifreq __user *ifr32)
3156-
{
3157-
struct compat_ethtool_rxnfc __user *compat_rxnfc;
3158-
bool convert_in = false, convert_out = false;
3159-
size_t buf_size = 0;
3160-
struct ethtool_rxnfc __user *rxnfc = NULL;
3161-
struct ifreq ifr;
3162-
u32 rule_cnt = 0, actual_rule_cnt;
3163-
u32 ethcmd;
3164-
u32 data;
3165-
int ret;
3166-
3167-
if (get_user(data, &ifr32->ifr_ifru.ifru_data))
3168-
return -EFAULT;
3169-
3170-
compat_rxnfc = compat_ptr(data);
3171-
3172-
if (get_user(ethcmd, &compat_rxnfc->cmd))
3173-
return -EFAULT;
3174-
3175-
/* Most ethtool structures are defined without padding.
3176-
* Unfortunately struct ethtool_rxnfc is an exception.
3177-
*/
3178-
switch (ethcmd) {
3179-
default:
3180-
break;
3181-
case ETHTOOL_GRXCLSRLALL:
3182-
/* Buffer size is variable */
3183-
if (get_user(rule_cnt, &compat_rxnfc->rule_cnt))
3184-
return -EFAULT;
3185-
if (rule_cnt > KMALLOC_MAX_SIZE / sizeof(u32))
3186-
return -ENOMEM;
3187-
buf_size += rule_cnt * sizeof(u32);
3188-
fallthrough;
3189-
case ETHTOOL_GRXRINGS:
3190-
case ETHTOOL_GRXCLSRLCNT:
3191-
case ETHTOOL_GRXCLSRULE:
3192-
case ETHTOOL_SRXCLSRLINS:
3193-
convert_out = true;
3194-
fallthrough;
3195-
case ETHTOOL_SRXCLSRLDEL:
3196-
buf_size += sizeof(struct ethtool_rxnfc);
3197-
convert_in = true;
3198-
rxnfc = compat_alloc_user_space(buf_size);
3199-
break;
3200-
}
3201-
3202-
if (copy_from_user(&ifr.ifr_name, &ifr32->ifr_name, IFNAMSIZ))
3203-
return -EFAULT;
3204-
3205-
ifr.ifr_data = convert_in ? rxnfc : (void __user *)compat_rxnfc;
3206-
3207-
if (convert_in) {
3208-
/* We expect there to be holes between fs.m_ext and
3209-
* fs.ring_cookie and at the end of fs, but nowhere else.
3210-
*/
3211-
BUILD_BUG_ON(offsetof(struct compat_ethtool_rxnfc, fs.m_ext) +
3212-
sizeof(compat_rxnfc->fs.m_ext) !=
3213-
offsetof(struct ethtool_rxnfc, fs.m_ext) +
3214-
sizeof(rxnfc->fs.m_ext));
3215-
BUILD_BUG_ON(
3216-
offsetof(struct compat_ethtool_rxnfc, fs.location) -
3217-
offsetof(struct compat_ethtool_rxnfc, fs.ring_cookie) !=
3218-
offsetof(struct ethtool_rxnfc, fs.location) -
3219-
offsetof(struct ethtool_rxnfc, fs.ring_cookie));
3220-
3221-
if (copy_in_user(rxnfc, compat_rxnfc,
3222-
(void __user *)(&rxnfc->fs.m_ext + 1) -
3223-
(void __user *)rxnfc) ||
3224-
copy_in_user(&rxnfc->fs.ring_cookie,
3225-
&compat_rxnfc->fs.ring_cookie,
3226-
(void __user *)(&rxnfc->fs.location + 1) -
3227-
(void __user *)&rxnfc->fs.ring_cookie))
3228-
return -EFAULT;
3229-
if (ethcmd == ETHTOOL_GRXCLSRLALL) {
3230-
if (put_user(rule_cnt, &rxnfc->rule_cnt))
3231-
return -EFAULT;
3232-
} else if (copy_in_user(&rxnfc->rule_cnt,
3233-
&compat_rxnfc->rule_cnt,
3234-
sizeof(rxnfc->rule_cnt)))
3235-
return -EFAULT;
3236-
}
3237-
3238-
ret = dev_ioctl(net, SIOCETHTOOL, &ifr, NULL);
3239-
if (ret)
3240-
return ret;
3241-
3242-
if (convert_out) {
3243-
if (copy_in_user(compat_rxnfc, rxnfc,
3244-
(const void __user *)(&rxnfc->fs.m_ext + 1) -
3245-
(const void __user *)rxnfc) ||
3246-
copy_in_user(&compat_rxnfc->fs.ring_cookie,
3247-
&rxnfc->fs.ring_cookie,
3248-
(const void __user *)(&rxnfc->fs.location + 1) -
3249-
(const void __user *)&rxnfc->fs.ring_cookie) ||
3250-
copy_in_user(&compat_rxnfc->rule_cnt, &rxnfc->rule_cnt,
3251-
sizeof(rxnfc->rule_cnt)))
3252-
return -EFAULT;
3253-
3254-
if (ethcmd == ETHTOOL_GRXCLSRLALL) {
3255-
/* As an optimisation, we only copy the actual
3256-
* number of rules that the underlying
3257-
* function returned. Since Mallory might
3258-
* change the rule count in user memory, we
3259-
* check that it is less than the rule count
3260-
* originally given (as the user buffer size),
3261-
* which has been range-checked.
3262-
*/
3263-
if (get_user(actual_rule_cnt, &rxnfc->rule_cnt))
3264-
return -EFAULT;
3265-
if (actual_rule_cnt < rule_cnt)
3266-
rule_cnt = actual_rule_cnt;
3267-
if (copy_in_user(&compat_rxnfc->rule_locs[0],
3268-
&rxnfc->rule_locs[0],
3269-
rule_cnt * sizeof(u32)))
3270-
return -EFAULT;
3271-
}
3272-
}
3273-
3274-
return 0;
3275-
}
3276-
32773155
static int compat_siocwandev(struct net *net, struct compat_ifreq __user *uifr32)
32783156
{
32793157
compat_uptr_t uptr32;
@@ -3428,8 +3306,6 @@ static int compat_sock_ioctl_trans(struct file *file, struct socket *sock,
34283306
return old_bridge_ioctl(argp);
34293307
case SIOCGIFCONF:
34303308
return compat_dev_ifconf(net, argp);
3431-
case SIOCETHTOOL:
3432-
return ethtool_ioctl(net, argp);
34333309
case SIOCWANDEV:
34343310
return compat_siocwandev(net, argp);
34353311
case SIOCGIFMAP:
@@ -3442,6 +3318,7 @@ static int compat_sock_ioctl_trans(struct file *file, struct socket *sock,
34423318
return sock->ops->gettstamp(sock, argp, cmd == SIOCGSTAMP_OLD,
34433319
!COMPAT_USE_64BIT_TIME);
34443320

3321+
case SIOCETHTOOL:
34453322
case SIOCBONDSLAVEINFOQUERY:
34463323
case SIOCBONDINFOQUERY:
34473324
case SIOCSHWTSTAMP:

0 commit comments

Comments
 (0)