mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-05-24 10:39:52 +00:00
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 84a1d9c482
("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>
This commit is contained in:
parent
1a33b18b3b
commit
dd98d2895d
3 changed files with 121 additions and 144 deletions
|
@ -17,8 +17,6 @@
|
||||||
#include <linux/compat.h>
|
#include <linux/compat.h>
|
||||||
#include <uapi/linux/ethtool.h>
|
#include <uapi/linux/ethtool.h>
|
||||||
|
|
||||||
#ifdef CONFIG_COMPAT
|
|
||||||
|
|
||||||
struct compat_ethtool_rx_flow_spec {
|
struct compat_ethtool_rx_flow_spec {
|
||||||
u32 flow_type;
|
u32 flow_type;
|
||||||
union ethtool_flow_union h_u;
|
union ethtool_flow_union h_u;
|
||||||
|
@ -38,8 +36,6 @@ struct compat_ethtool_rxnfc {
|
||||||
u32 rule_locs[];
|
u32 rule_locs[];
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* CONFIG_COMPAT */
|
|
||||||
|
|
||||||
#include <linux/rculist.h>
|
#include <linux/rculist.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
* the information ethtool needs.
|
* the information ethtool needs.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <linux/compat.h>
|
||||||
#include <linux/module.h>
|
#include <linux/module.h>
|
||||||
#include <linux/types.h>
|
#include <linux/types.h>
|
||||||
#include <linux/capability.h>
|
#include <linux/capability.h>
|
||||||
|
@ -807,6 +808,120 @@ out:
|
||||||
return ret;
|
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,
|
static noinline_for_stack int ethtool_set_rxnfc(struct net_device *dev,
|
||||||
u32 cmd, void __user *useraddr)
|
u32 cmd, void __user *useraddr)
|
||||||
{
|
{
|
||||||
|
@ -825,7 +940,7 @@ static noinline_for_stack int ethtool_set_rxnfc(struct net_device *dev,
|
||||||
info_size = (offsetof(struct ethtool_rxnfc, data) +
|
info_size = (offsetof(struct ethtool_rxnfc, data) +
|
||||||
sizeof(info.data));
|
sizeof(info.data));
|
||||||
|
|
||||||
if (copy_from_user(&info, useraddr, info_size))
|
if (ethtool_rxnfc_copy_from_user(&info, useraddr, info_size))
|
||||||
return -EFAULT;
|
return -EFAULT;
|
||||||
|
|
||||||
rc = dev->ethtool_ops->set_rxnfc(dev, &info);
|
rc = dev->ethtool_ops->set_rxnfc(dev, &info);
|
||||||
|
@ -833,7 +948,7 @@ static noinline_for_stack int ethtool_set_rxnfc(struct net_device *dev,
|
||||||
return rc;
|
return rc;
|
||||||
|
|
||||||
if (cmd == ETHTOOL_SRXCLSRLINS &&
|
if (cmd == ETHTOOL_SRXCLSRLINS &&
|
||||||
copy_to_user(useraddr, &info, info_size))
|
ethtool_rxnfc_copy_to_user(useraddr, &info, info_size, NULL))
|
||||||
return -EFAULT;
|
return -EFAULT;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -859,7 +974,7 @@ static noinline_for_stack int ethtool_get_rxnfc(struct net_device *dev,
|
||||||
info_size = (offsetof(struct ethtool_rxnfc, data) +
|
info_size = (offsetof(struct ethtool_rxnfc, data) +
|
||||||
sizeof(info.data));
|
sizeof(info.data));
|
||||||
|
|
||||||
if (copy_from_user(&info, useraddr, info_size))
|
if (ethtool_rxnfc_copy_from_user(&info, useraddr, info_size))
|
||||||
return -EFAULT;
|
return -EFAULT;
|
||||||
|
|
||||||
/* If FLOW_RSS was requested then user-space must be using the
|
/* If FLOW_RSS was requested then user-space must be using the
|
||||||
|
@ -867,7 +982,7 @@ static noinline_for_stack int ethtool_get_rxnfc(struct net_device *dev,
|
||||||
*/
|
*/
|
||||||
if (cmd == ETHTOOL_GRXFH && info.flow_type & FLOW_RSS) {
|
if (cmd == ETHTOOL_GRXFH && info.flow_type & FLOW_RSS) {
|
||||||
info_size = sizeof(info);
|
info_size = sizeof(info);
|
||||||
if (copy_from_user(&info, useraddr, info_size))
|
if (ethtool_rxnfc_copy_from_user(&info, useraddr, info_size))
|
||||||
return -EFAULT;
|
return -EFAULT;
|
||||||
/* Since malicious users may modify the original data,
|
/* Since malicious users may modify the original data,
|
||||||
* we need to check whether FLOW_RSS is still requested.
|
* 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,
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
goto err_out;
|
goto err_out;
|
||||||
|
|
||||||
ret = -EFAULT;
|
ret = ethtool_rxnfc_copy_to_user(useraddr, &info, info_size, rule_buf);
|
||||||
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;
|
|
||||||
|
|
||||||
err_out:
|
err_out:
|
||||||
kfree(rule_buf);
|
kfree(rule_buf);
|
||||||
|
|
||||||
|
|
125
net/socket.c
125
net/socket.c
|
@ -3152,128 +3152,6 @@ static int compat_dev_ifconf(struct net *net, struct compat_ifconf __user *uifc3
|
||||||
return 0;
|
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)
|
static int compat_siocwandev(struct net *net, struct compat_ifreq __user *uifr32)
|
||||||
{
|
{
|
||||||
compat_uptr_t uptr32;
|
compat_uptr_t uptr32;
|
||||||
|
@ -3428,8 +3306,6 @@ static int compat_sock_ioctl_trans(struct file *file, struct socket *sock,
|
||||||
return old_bridge_ioctl(argp);
|
return old_bridge_ioctl(argp);
|
||||||
case SIOCGIFCONF:
|
case SIOCGIFCONF:
|
||||||
return compat_dev_ifconf(net, argp);
|
return compat_dev_ifconf(net, argp);
|
||||||
case SIOCETHTOOL:
|
|
||||||
return ethtool_ioctl(net, argp);
|
|
||||||
case SIOCWANDEV:
|
case SIOCWANDEV:
|
||||||
return compat_siocwandev(net, argp);
|
return compat_siocwandev(net, argp);
|
||||||
case SIOCGIFMAP:
|
case SIOCGIFMAP:
|
||||||
|
@ -3442,6 +3318,7 @@ static int compat_sock_ioctl_trans(struct file *file, struct socket *sock,
|
||||||
return sock->ops->gettstamp(sock, argp, cmd == SIOCGSTAMP_OLD,
|
return sock->ops->gettstamp(sock, argp, cmd == SIOCGSTAMP_OLD,
|
||||||
!COMPAT_USE_64BIT_TIME);
|
!COMPAT_USE_64BIT_TIME);
|
||||||
|
|
||||||
|
case SIOCETHTOOL:
|
||||||
case SIOCBONDSLAVEINFOQUERY:
|
case SIOCBONDSLAVEINFOQUERY:
|
||||||
case SIOCBONDINFOQUERY:
|
case SIOCBONDINFOQUERY:
|
||||||
case SIOCSHWTSTAMP:
|
case SIOCSHWTSTAMP:
|
||||||
|
|
Loading…
Add table
Reference in a new issue