hwmon: (ina2xx) Fix various overflow issues

Sashiko reports several integer overflow problems in the ina2xx driver
caused by unbounded multiplications and inadequate types for intermediate
calculations.

Specifically:
- In ina2xx_get_value(), the return type is changed from int to long.
  Intermediate calculations for current are now performed using 64-bit
  types to prevent 32-bit integer overflow before the division by 1000.
- When calculating power in ina2xx_get_value() and
  sy24655_average_power_read(), interim values are cast to u64 and clamped
  to LONG_MAX. This prevents overflow when regval or accumulator_24 is
  multiplied by power_lsb_uW.
- In ina226_alert_to_reg(), the clamping logic is rewritten using min_t().
  This safely avoids integer overflows when scaling user-provided values
  for shunt voltage, bus voltage, power, and current limits.

Cc: Loic Poulain <loic.poulain@oss.qualcomm.com>
Fixes: ab7fbee452 ("hwmon: (ina2xx) Fix various overflow issues")
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
This commit is contained in:
Guenter Roeck
2026-06-10 07:46:16 -07:00
parent a64a7e8a0b
commit e6c80061ca

View File

@@ -16,6 +16,7 @@
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/limits.h>
#include <linux/module.h>
#include <linux/property.h>
#include <linux/regmap.h>
@@ -266,30 +267,34 @@ static u16 ina226_interval_to_reg(long interval)
return FIELD_PREP(INA226_AVG_RD_MASK, avg_bits);
}
static int ina2xx_get_value(struct ina2xx_data *data, u8 reg,
unsigned int regval)
static long ina2xx_get_value(struct ina2xx_data *data, u8 reg,
unsigned int regval)
{
int val;
s64 val64;
long val;
switch (reg) {
case INA2XX_SHUNT_VOLTAGE:
/* signed register */
val = (s16)regval >> data->config->shunt_voltage_shift;
val = DIV_ROUND_CLOSEST(val, data->config->shunt_div);
val = DIV_ROUND_CLOSEST((s16)regval >> data->config->shunt_voltage_shift,
data->config->shunt_div);
break;
case INA2XX_BUS_VOLTAGE:
val = (regval >> data->config->bus_voltage_shift) *
data->config->bus_voltage_lsb;
val = DIV_ROUND_CLOSEST(val, 1000);
val = DIV_ROUND_CLOSEST((regval >> data->config->bus_voltage_shift) *
data->config->bus_voltage_lsb, 1000);
break;
case INA2XX_POWER:
val = regval * data->power_lsb_uW;
val = min_t(u64, (u64)regval * data->power_lsb_uW, LONG_MAX);
break;
case INA2XX_CURRENT:
/* signed register, result in mA */
val = ((s16)regval >> data->config->current_shift) *
val64 = (s64)((s16)regval >> data->config->current_shift) *
data->current_lsb_uA;
val = DIV_ROUND_CLOSEST(val, 1000);
if (val64 < 0)
val64 = -DIV_ROUND_CLOSEST_ULL(-val64, 1000);
else
val64 = DIV_ROUND_CLOSEST_ULL(val64, 1000);
val = clamp_val(val64, LONG_MIN, LONG_MAX);
break;
case INA2XX_CALIBRATION:
val = regval;
@@ -378,27 +383,29 @@ static int ina2xx_read_init(struct device *dev, int reg, long *val)
*/
static u16 ina226_alert_to_reg(struct ina2xx_data *data, int reg, long val)
{
long limit;
switch (reg) {
case INA2XX_SHUNT_VOLTAGE:
val = clamp_val(val, 0, SHRT_MAX * data->config->shunt_div);
val *= data->config->shunt_div;
val <<= data->config->shunt_voltage_shift;
return clamp_val(val, 0, SHRT_MAX);
val = min_t(long, val, DIV_ROUND_CLOSEST(SHRT_MAX, data->config->shunt_div));
return min_t(long, (val * data->config->shunt_div) << data->config->shunt_voltage_shift,
SHRT_MAX);
case INA2XX_BUS_VOLTAGE:
val = clamp_val(val, 0, 200000);
val = (val * 1000) << data->config->bus_voltage_shift;
val = DIV_ROUND_CLOSEST(val, data->config->bus_voltage_lsb);
return clamp_val(val, 0, USHRT_MAX);
val = min_t(long, val, 130000);
return min_t(long,
DIV_ROUND_CLOSEST((val * 1000) << data->config->bus_voltage_shift,
data->config->bus_voltage_lsb),
USHRT_MAX);
case INA2XX_POWER:
val = clamp_val(val, 0, UINT_MAX - data->power_lsb_uW);
val = DIV_ROUND_CLOSEST(val, data->power_lsb_uW);
return clamp_val(val, 0, USHRT_MAX);
val = min_t(long, val, LONG_MAX - data->power_lsb_uW);
return min_t(long, DIV_ROUND_CLOSEST(val, data->power_lsb_uW), USHRT_MAX);
case INA2XX_CURRENT:
val = clamp_val(val, INT_MIN / 1000, INT_MAX / 1000);
limit = (LONG_MAX - (data->current_lsb_uA / 2)) / 1000;
val = min_t(long, val, limit);
/* signed register, result in mA */
val = DIV_ROUND_CLOSEST(val * 1000, data->current_lsb_uA);
val <<= data->config->current_shift;
return clamp_val(val, SHRT_MIN, SHRT_MAX);
limit = SHRT_MAX >> data->config->current_shift;
return (u16)(min_t(long, val, limit) << data->config->current_shift);
default:
/* programmer goofed */
WARN_ON_ONCE(1);
@@ -537,6 +544,7 @@ static int sy24655_average_power_read(struct ina2xx_data *data, u8 reg, long *va
u8 template[6];
int ret;
long accumulator_24, sample_count;
u64 val64;
/* 48-bit register read */
ret = i2c_smbus_read_i2c_block_data(data->client, reg, 6, template);
@@ -555,7 +563,8 @@ static int sy24655_average_power_read(struct ina2xx_data *data, u8 reg, long *va
return 0;
}
*val = DIV_ROUND_CLOSEST(accumulator_24, sample_count) * data->power_lsb_uW;
val64 = (u64)DIV_ROUND_CLOSEST(accumulator_24, sample_count) * data->power_lsb_uW;
*val = min_t(u64, val64, LONG_MAX);
return 0;
}