Skip to content

Commit

Permalink
net/ethtool/ioctl: split ethtool_get_phy_stats into multiple helpers
Browse files Browse the repository at this point in the history
So that it's easier to follow and make sense of the branching and
various conditions.

Stats retrieval has been split into two separate functions
ethtool_get_phy_stats_phydev & ethtool_get_phy_stats_ethtool.
The former attempts to retrieve the stats using phydev & phy_ops, while
the latter uses ethtool_ops.

Actual n_stats validation & array allocation has been moved into a new
ethtool_vzalloc_stats_array helper.

This also fixes a potential NULL dereference of
ops->get_ethtool_phy_stats where it was getting called in an else branch
unconditionally without making sure it was actually present.

Found by Linux Verification Center (linuxtesting.org) with the SVACE
static analysis tool.

Signed-off-by: Daniil Tatianin <[email protected]>
Reviewed-by: Andrew Lunn <[email protected]>
Signed-off-by: David S. Miller <[email protected]>
  • Loading branch information
d-tatianin authored and davem330 committed Dec 28, 2022
1 parent fd47785 commit 201ed31
Showing 1 changed file with 69 additions and 33 deletions.
102 changes: 69 additions & 33 deletions net/ethtool/ioctl.c
Original file line number Diff line number Diff line change
Expand Up @@ -2078,55 +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;
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);

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);
}
if (ret)
goto out;

ret = -EFAULT;
if (copy_to_user(useraddr, &stats, sizeof(stats)))
if (copy_to_user(useraddr, &stats, sizeof(stats))) {
ret = -EFAULT;
goto out;
}

useraddr += sizeof(stats);
if (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);
Expand Down

0 comments on commit 201ed31

Please sign in to comment.