# DF-1137 — Verdict

## Verdict: REPRODUCED (source-level + harness) — divide-by-zero DoS, no escalation chain (no corruption primitive)

## Bug confirmation

`si_thermal_setup_fan_table` (radeon/si_dpm.c:6057-6064) computes two
temperature deltas from BIOS-controlled u16 fan-table fields and uses
them as divisors with no zero-check:

```c
t_diff1 = rdev->pm.dpm.fan.t_med - rdev->pm.dpm.fan.t_min;   /* line 6057 */
t_diff2 = rdev->pm.dpm.fan.t_high - rdev->pm.dpm.fan.t_med;  /* line 6058 */
...
slope1 = (u16)((50 + ((16 * duty100 * pwm_diff1) / t_diff1)) / 100);  /* line 6063 */
slope2 = (u16)((50 + ((16 * duty100 * pwm_diff2) / t_diff2)) / 100);  /* line 6064 */
```

The fan-table fields are populated directly from the BIOS `usTMin` /
`usTMed` / `usTHigh` u16 values at **r600_dpm.c:897-899** with no
validation:

```c
rdev->pm.dpm.fan.t_min  = le16_to_cpu(fan_info->fan.usTMin);
rdev->pm.dpm.fan.t_med  = le16_to_cpu(fan_info->fan.usTMed);
rdev->pm.dpm.fan.t_high = le16_to_cpu(fan_info->fan.usTHigh);
```

So a crafted VBIOS where `usTMed == usTMin` (or `usTHigh == usTMed`)
makes `t_diff1 == 0` (or `t_diff2 == 0`), and the kernel hits an integer
divide-by-zero on line 6063 (or 6064). On x86 this traps as `#DE` ->
kernel panic.

The same bug class also exists in `ci_dpm.c:1018-1019` (CIK parts); that's
out of scope for this finding but worth noting as a sibling fix.

## Harness confirmation

`harness.c` mirrors the kernel math. When run with
`fan.t_min = fan.t_med = 5000`, the buggy computation raises `SIGFPE`
(integer divide-by-zero), which the harness traps and reports as the
kernel-equivalent `#DE` panic. The fixed code path (with the zero-delta
guard) rejects the same input with `-EINVAL` and skips the divide.

Output captured in `run.log`:
```
attempting buggy version (mirrors si_dpm.c:6046-6064)...
  buggy: SIGFPE / #DE divide-by-zero raised!
  >>> In kernel context this is a kernel panic. <<<

attempting fixed version (mirrors fix.diff)...
  fixed: REJECTED zero delta (t_min=5000 t_med=5000 t_high=9000) -> -EINVAL
```

## Exploit chain

This is a **pure divide-by-zero** — no memory corruption, no read or
write primitive. The realistic impact is purely a DoS: kernel panic at
DPM enable time, which on default configurations happens at every boot
when `ucode_fan_control=true` and a crafted VBIOS is present.

No escalation chain possible. The bug is a clean DoS.

## Trigger conditions (not met on this guest)

1. AMD SI GPU present (no AMD GPU on the QEMU audit guest).
2. `radeon.ko` loaded.
3. Crafted VBIOS with `usTMed == usTMin` or `usTHigh == usTMed`.
4. `rdev->pm.dpm.fan.ucode_fan_control == true` (set when BIOS fan table
   is present and `duty100 != 0` — verified at si_dpm.c:6046-6051).

Source-level + harness-confirmed; no live runtime trigger on the guest.

## Fix

`fix.diff` adds the missing zero-delta check immediately after
`t_diff2` is computed:
```c
if (t_diff1 == 0 || t_diff2 == 0) {
    dev_err(rdev->dev, "invalid thermal fan table: zero temperature delta (...)\n", ...);
    rdev->pm.dpm.fan.ucode_fan_control = false;
    return -EINVAL;
}
```
This mirrors the existing `duty100 == 0` early-out at lines 6048-6051.

## Fix validation

1. `patch -p1 --check` — clean apply, 1 hunk.
2. `cd /usr/src/sys/dev/drm/radeon && make` with the diff applied —
   `radeon.ko` built cleanly (`rc=0`, 2,029,496 bytes).
3. Reverted.

Since the bug cannot be triggered live on the guest, the behaviour
comparison is at the harness level: `run.log` shows the buggy path
raising `SIGFPE` while the fixed path returns `-EINVAL`.

`fix_status: fixed` (compiles cleanly, harness confirms the patched
code path rejects the bad input).
