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 intUINT_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) / divisorkernel division by zero → oops/panic.

The same overflow path applies to exp = -8 (6 + (-8) = -2UINT_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)
  1. Attacker plugs in a USB HID sensor with unit_expo = 0x09 in its HID report descriptor.
  2. Kernel enumerates the device; st->sensitivity.unit_expo = 0x09.
  3. Any user writes a sensitivity value to the IIO sysfs file.
  4. Kernel calls hid_sensor_write_raw_hyst_value()convert_to_vtf_format(..., st->sensitivity.unit_expo, val1, val2).
  5. exp = hid_sensor_convert_exponent(0x09) = -7divisor = int_pow(10, UINT_MAX) = 0divide-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 on exp
  • drivers/iio/common/hid-sensors/hid-sensor-attributes.c — lines 145, 99, 102 — negative exponents passed to int_pow without bounds check
  • include/linux/hid-sensor-hub.h:241hid_sensor_convert_exponent can return -7 or -8