]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
hwmon: (ina2xx) Fix various overflow issues
authorGuenter Roeck <linux@roeck-us.net>
Wed, 10 Jun 2026 14:46:16 +0000 (07:46 -0700)
committerGuenter Roeck <linux@roeck-us.net>
Mon, 27 Jul 2026 18:34:24 +0000 (11:34 -0700)
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: ab7fbee452be ("hwmon: (ina2xx) Fix various overflow issues")
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
drivers/hwmon/ina2xx.c

index c4742e84b999d4079a92d6152c720de8b679874c..449a72c6b40bde01b15becff9f9d1039deb7d8cf 100644 (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;
 }