From d6cc7c99bf1f73eda7d565d224d791d16239bb41 Mon Sep 17 00:00:00 2001 From: Sanman Pradhan Date: Thu, 16 Apr 2026 21:59:30 +0000 Subject: hwmon: (ltc2992) Clamp threshold writes to hardware range ltc2992_set_voltage(), ltc2992_set_current(), and ltc2992_set_power() do not validate the user-supplied value before converting it to a register value. This can result in: 1. Negative input values wrapping to large positive register values. For power, the negative long is implicitly cast to u64 in mul_u64_u32_div(), producing an incorrect value. For voltage and current, the negative converted value wraps when passed to ltc2992_write_reg() as a u32. 2. Intermediate arithmetic exceeding the range representable in u64 on 64-bit platforms. In ltc2992_set_voltage(), (u64)val * 1000 can exceed U64_MAX when val is a large positive long. In ltc2992_set_current(), (u64)val * r_sense_uohm can overflow similarly. In ltc2992_set_power(), the computed value may not fit in u64. 3. Register values exceeding the hardware field width. Voltage and current threshold registers are 12-bit (stored left-justified in 16 bits), and power threshold registers are 24-bit. Without clamping, bits above the field width are truncated in ltc2992_write_reg(). Fix by clamping negative values to zero, clamping positive values to the rounded hardware-representable maximum (the value returned by the read path for a full-scale register) to prevent intermediate overflow, and clamping the converted register value to the hardware field width before writing. The existing conversion formula and rounding behavior are preserved. In the power write path, cancel the factor of 1000 from both the numerator (r_sense_uohm * 1000) and the denominator (VADC_UV_LSB * IADC_NANOV_LSB) to also eliminate a u32 overflow of r_sense_uohm * 1000 when r_sense_uohm exceeds about 4.29 ohms. Fixes: b0bd407e94b03 ("hwmon: (ltc2992) Add support") Cc: stable@vger.kernel.org Signed-off-by: Sanman Pradhan Link: https://lore.kernel.org/r/20260416215904.101969-2-sanman.pradhan@hpe.com Signed-off-by: Guenter Roeck --- drivers/hwmon/ltc2992.c | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/drivers/hwmon/ltc2992.c b/drivers/hwmon/ltc2992.c index 1fcd320d6161..106973619676 100644 --- a/drivers/hwmon/ltc2992.c +++ b/drivers/hwmon/ltc2992.c @@ -431,10 +431,16 @@ static int ltc2992_get_voltage(struct ltc2992_state *st, u32 reg, u32 scale, lon static int ltc2992_set_voltage(struct ltc2992_state *st, u32 reg, u32 scale, long val) { - val = DIV_ROUND_CLOSEST(val * 1000, scale); - val = val << 4; + u32 reg_val; + long vmax; + + vmax = DIV_ROUND_CLOSEST_ULL(0xFFFULL * scale, 1000); + val = max(val, 0L); + val = min(val, vmax); + reg_val = min(DIV_ROUND_CLOSEST_ULL((u64)val * 1000, scale), + 0xFFFULL) << 4; - return ltc2992_write_reg(st, reg, 2, val); + return ltc2992_write_reg(st, reg, 2, reg_val); } static int ltc2992_read_gpio_alarm(struct ltc2992_state *st, int nr_gpio, u32 attr, long *val) @@ -559,9 +565,15 @@ static int ltc2992_get_current(struct ltc2992_state *st, u32 reg, u32 channel, l static int ltc2992_set_current(struct ltc2992_state *st, u32 reg, u32 channel, long val) { u32 reg_val; + long cmax; - reg_val = DIV_ROUND_CLOSEST(val * st->r_sense_uohm[channel], LTC2992_IADC_NANOV_LSB); - reg_val = reg_val << 4; + cmax = DIV_ROUND_CLOSEST_ULL(0xFFFULL * LTC2992_IADC_NANOV_LSB, + st->r_sense_uohm[channel]); + val = max(val, 0L); + val = min(val, cmax); + reg_val = min(DIV_ROUND_CLOSEST_ULL((u64)val * st->r_sense_uohm[channel], + LTC2992_IADC_NANOV_LSB), + 0xFFFULL) << 4; return ltc2992_write_reg(st, reg, 2, reg_val); } @@ -634,9 +646,18 @@ static int ltc2992_get_power(struct ltc2992_state *st, u32 reg, u32 channel, lon static int ltc2992_set_power(struct ltc2992_state *st, u32 reg, u32 channel, long val) { u32 reg_val; - - reg_val = mul_u64_u32_div(val, st->r_sense_uohm[channel] * 1000, - LTC2992_VADC_UV_LSB * LTC2992_IADC_NANOV_LSB); + u64 pmax, uval; + + uval = max(val, 0L); + pmax = mul_u64_u32_div(0xFFFFFFULL, + LTC2992_VADC_UV_LSB / 1000 * + LTC2992_IADC_NANOV_LSB, + st->r_sense_uohm[channel]); + uval = min(uval, pmax); + reg_val = min(mul_u64_u32_div(uval, st->r_sense_uohm[channel], + LTC2992_VADC_UV_LSB / 1000 * + LTC2992_IADC_NANOV_LSB), + 0xFFFFFFULL); return ltc2992_write_reg(st, reg, 3, reg_val); } -- cgit v1.2.3