Merge branch 'ethtool_gert_phy_stats-fixes'

Daniil Tatianin says:

====================
net/ethtool/ioctl: split ethtool_get_phy_stats into multiple helpers

This series fixes a potential NULL dereference in ethtool_get_phy_stats
while also attempting to refactor/split said function into multiple
helpers so that it's easier to reason about what's going on.

I've taken Andrew Lunn's suggestions on the previous version of this
patch and added a bit of my own.

Changes since v1:
- Remove an extra newline in the first patch
- Move WARN_ON_ONCE into the if check as it already returns the
  result of the comparison
- Actually split ethtool_get_phy_stats instead of attempting to
  refactor it
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
David S. Miller 2022-12-28 11:55:24 +00:00
commit e71460d446

View file

@ -2078,58 +2078,91 @@ static int ethtool_get_stats(struct net_device *dev, void __user *useraddr)
return ret;
}
static int ethtool_get_phy_stats(struct net_device *dev, void __user *useraddr)
static int ethtool_vzalloc_stats_array(int n_stats, u64 **data)
{
const struct ethtool_phy_ops *phy_ops = ethtool_phy_ops;
const struct ethtool_ops *ops = dev->ethtool_ops;
struct phy_device *phydev = dev->phydev;
struct ethtool_stats stats;
u64 *data;
int ret, n_stats;
if (!phydev && (!ops->get_ethtool_phy_stats || !ops->get_sset_count))
return -EOPNOTSUPP;
if (phydev && !ops->get_ethtool_phy_stats &&
phy_ops && phy_ops->get_sset_count)
n_stats = phy_ops->get_sset_count(phydev);
else
n_stats = ops->get_sset_count(dev, ETH_SS_PHY_STATS);
if (n_stats < 0)
return n_stats;
if (n_stats > S32_MAX / sizeof(u64))
return -ENOMEM;
WARN_ON_ONCE(!n_stats);
if (WARN_ON_ONCE(!n_stats))
return -EOPNOTSUPP;
*data = vzalloc(array_size(n_stats, sizeof(u64)));
if (!*data)
return -ENOMEM;
return 0;
}
static int ethtool_get_phy_stats_phydev(struct phy_device *phydev,
struct ethtool_stats *stats,
u64 **data)
{
const struct ethtool_phy_ops *phy_ops = ethtool_phy_ops;
int n_stats, ret;
if (!phy_ops || !phy_ops->get_sset_count || !phy_ops->get_stats)
return -EOPNOTSUPP;
n_stats = phy_ops->get_sset_count(phydev);
ret = ethtool_vzalloc_stats_array(n_stats, data);
if (ret)
return ret;
stats->n_stats = n_stats;
return phy_ops->get_stats(phydev, stats, *data);
}
static int ethtool_get_phy_stats_ethtool(struct net_device *dev,
struct ethtool_stats *stats,
u64 **data)
{
const struct ethtool_ops *ops = dev->ethtool_ops;
int n_stats, ret;
if (!ops || !ops->get_sset_count || ops->get_ethtool_phy_stats)
return -EOPNOTSUPP;
n_stats = ops->get_sset_count(dev, ETH_SS_PHY_STATS);
ret = ethtool_vzalloc_stats_array(n_stats, data);
if (ret)
return ret;
stats->n_stats = n_stats;
ops->get_ethtool_phy_stats(dev, stats, *data);
return 0;
}
static int ethtool_get_phy_stats(struct net_device *dev, void __user *useraddr)
{
struct phy_device *phydev = dev->phydev;
struct ethtool_stats stats;
u64 *data = NULL;
int ret = -EOPNOTSUPP;
if (copy_from_user(&stats, useraddr, sizeof(stats)))
return -EFAULT;
stats.n_stats = n_stats;
if (phydev)
ret = ethtool_get_phy_stats_phydev(phydev, &stats, &data);
if (n_stats) {
data = vzalloc(array_size(n_stats, sizeof(u64)));
if (!data)
return -ENOMEM;
if (ret == -EOPNOTSUPP)
ret = ethtool_get_phy_stats_ethtool(dev, &stats, &data);
if (phydev && !ops->get_ethtool_phy_stats &&
phy_ops && phy_ops->get_stats) {
ret = phy_ops->get_stats(phydev, &stats, data);
if (ret < 0)
goto out;
} else {
ops->get_ethtool_phy_stats(dev, &stats, data);
}
} else {
data = NULL;
if (ret)
goto out;
if (copy_to_user(useraddr, &stats, sizeof(stats))) {
ret = -EFAULT;
goto out;
}
ret = -EFAULT;
if (copy_to_user(useraddr, &stats, sizeof(stats)))
goto out;
useraddr += sizeof(stats);
if (n_stats && copy_to_user(useraddr, data, array_size(n_stats, sizeof(u64))))
goto out;
ret = 0;
if (copy_to_user(useraddr, data, array_size(stats.n_stats, sizeof(u64))))
ret = -EFAULT;
out:
vfree(data);