CVE-Candidate: Kernel Divide-by-Zero via Malicious USB HID Sensor (hid-sensor-attributes)
Summary
A kernel divide-by-zero can be triggered by a malicious USB HID sensor device that crafts specific unit_expo values in its HID report descriptor. The root cause is an implicit negative-to-unsigned integer conversion when calling int_pow(), causing it to return 0, which is then used as a divisor — crashing the kernel.
Severity: High (Kernel Panic / Denial of Service)
Attack Vector: Physical (USB) / Guest-to-Host in virtualized environments
Affected component: drivers/iio/common/hid-sensors/hid-sensor-attributes.c
Root cause: lib/math/int_pow.c — no guard against implicit signed→unsigned conversion of negative exponents
Vulnerable Code
lib/math/int_pow.c — the primitive
u64 int_pow(u64 base, unsigned int exp) // exp is unsigned
{
u64 result = 1;
while (exp) {
if (exp & 1)
result *= base;
exp >>= 1;
base *= base;
}
return result;
}
The function silently accepts any unsigned int. Callers that pass negative int values trigger implicit conversion to huge unsigned numbers.
include/linux/hid-sensor-hub.h — exponent decoding
static inline int hid_sensor_convert_exponent(int unit_expo)
{
if (unit_expo < 0x08)
return unit_expo; // 0..7
else if (unit_expo <= 0x0f)
return -(0x0f - unit_expo + 1); // -8..-1
else
return 0;
}
A HID device controls unit_expo (4-bit nibble in the report descriptor). Values 0x08 and 0x09 yield exponents -8 and -7 respectively.
drivers/iio/common/hid-sensors/hid-sensor-attributes.c:135-156 — the crash site
static u32 convert_to_vtf_format(int size, int exp, int val1, int val2)
{
int divisor;
u32 value;
...
exp = hid_sensor_convert_exponent(exp);
if (exp < 0) {
divisor = int_pow(10, 6 + exp); // ← line 145: BUG
value = abs(val1) * int_pow(10, -exp);
value += abs(val2) / divisor; // ← line 147: DIVIDE BY ZERO
}
...
}
When exp = -7: 6 + (-7) = -1. The -1 (type int) is implicitly cast to unsigned int → UINT_MAX (0xFFFFFFFF) before being passed to int_pow.
int_pow(10, UINT_MAX): The binary exponentiation runs 32 iterations (since UINT_MAX has 32 bits). At each step base *= base squares the value mod 2⁶⁴. Because 10 = 2 × 5, every squaring adds factors of 2. After ~20 squarings, base mod 2⁶⁴ = 0. All subsequent result *= base multiply by 0, so the function returns 0.
Result: divisor = 0, then abs(val2) / divisor → kernel division by zero → oops/panic.
The same overflow path applies to exp = -8 (6 + (-8) = -2 → UINT_MAX - 1 → same result of 0).
Trigger Conditions
The vulnerable path is reached in two ways:
Path 1 — Writing sensitivity (user-triggered, physical attacker)
/sys/bus/iio/devices/iio:deviceN/in_accel_x_thresh_rising_value (write)
- Attacker plugs in a USB HID sensor with
unit_expo = 0x09in its HID report descriptor. - Kernel enumerates the device;
st->sensitivity.unit_expo = 0x09. - Any user writes a sensitivity value to the IIO sysfs file.
- Kernel calls
hid_sensor_write_raw_hyst_value()→convert_to_vtf_format(..., st->sensitivity.unit_expo, val1, val2). exp = hid_sensor_convert_exponent(0x09) = -7→divisor = int_pow(10, UINT_MAX) = 0→ divide-by-zero.
Path 2 — Reading sensitivity (automatic on device open)
hid_sensor_read_raw_hyst_value() calls convert_from_vtf_format() which calls split_micro_fraction(value, 8, ...) when exp = -8, computing int_pow(10, 6 - 8) = int_pow(10, UINT_MAX) = 0, and then no % 0 or no / 0 — also undefined behavior / divide-by-zero.
Proof-of-Concept (HID Descriptor Snippet)
A malicious HID report descriptor advertising a sensor with a crafted unit exponent:
...
0x55, 0x09, // Unit Exponent (0x09 → decoded as -7)
...
Such a descriptor can be emitted by a cheap USB microcontroller (e.g., ATmega32U4, RP2040 in TinyUSB) acting as a HID sensor hub.
Impact
| Property | Detail |
|---|---|
| Effect | Kernel oops / panic (divide by zero in interrupt-safe kernel context) |
| Availability | Complete — system requires reboot |
| Confidentiality | None |
| Integrity | None |
| Privileges required | Physical USB access (or malicious VM guest) |
| User interaction | None after device plug-in; triggered on IIO sysfs read/write |
With oops=panic (common in production kernels), this is a full system crash from a plugged-in USB device.
Fix
Option A — Guard int_pow callers
In convert_to_vtf_format and split_micro_fraction, clamp the exponent before passing to int_pow:
// Before: divisor = int_pow(10, 6 + exp);
// After:
int e = 6 + exp;
if (e < 0) {
/* exponent out of range — return safe fallback */
return 0;
}
divisor = int_pow(10, (unsigned int)e);
if (divisor == 0)
return 0;
value += abs(val2) / divisor;
Option B — Validate unit_expo at device enumeration time
Reject or clamp unit_expo values that would produce exponents outside a safe range (e.g., [-6, 6]) when the HID device is first registered, before they reach the conversion path.
Affected Files
lib/math/int_pow.c— no input validation onexpdrivers/iio/common/hid-sensors/hid-sensor-attributes.c— lines 145, 99, 102 — negative exponents passed toint_powwithout bounds checkinclude/linux/hid-sensor-hub.h:241—hid_sensor_convert_exponentcan return -7 or -8