mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-31 11:41:29 -04:00
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:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user