# DF-1297 — Divide-by-zero in `ci_thermal_setup_fan_table` (ci_smumgr.c)

## Verdict: REPRODUCED (source-level + harness) — latent AMD-DRM bug, panic/DoS

The AMD GPU DRM driver (`amdgpu` / radeon powerplay) is **not compiled into
the DragonFlyBSD `X86_64_GENERIC` kernel** and **no AMD GPU hardware is present
on the audit guest**, so the bug cannot be triggered end-to-end here. It is a
**real latent bug** in the loadable `amdgpu` module: confirmed by source trace
and reproduced at the object/arithmetic level with a userspace harness that
mirrors the vulnerable function exactly.

## The bug

`sys/dev/drm/amd/powerplay/smumgr/ci_smumgr.c`, function
`ci_thermal_setup_fan_table`, lines 2159-2166:

```c
t_diff1 = ...usTMed - ...usTMin;     /* VBIOS-controlled */
t_diff2 = ...usTHigh - ...usTMed;    /* VBIOS-controlled */
...
slope1 = (uint16_t)((50 + ((16 * duty100 * pwm_diff1) / t_diff1)) / 100);  /* :2165 */
slope2 = (uint16_t)((50 + ((16 * duty100 * pwm_diff2) / t_diff2)) / 100);  /* :2166 */
```

`t_diff1` and `t_diff2` are derived from `usTMin`/`usTMed`/`usTHigh`, which are
parsed directly out of the GPU **VBIOS** (`ATOM_Tonga_Fan_Table`) at
`sys/dev/drm/amd/powerplay/hwmgr/process_pptables_v1_0.c:941-946` with **no
ordering or zero-delta validation**. A crafted or faulty VBIOS in which
`usTMed == usTMin` makes `t_diff1 == 0`, and the division at line 2165 raises
CPU trap 0 (#DE) → **kernel panic**. Symmetrically for `usTHigh == usTMed`
at line 2166.

## Reachability / threat model

The function is only entered when all of these hold (all satisfied on a real
CIK/Bonaire/Hawaii card with microcode fan control, which is the common case):

- `PHM_PlatformCaps_MicrocodeFanControl` is set (`ci_smumgr.c:2134`) — set by
  the VBIOS fan-table presence at `process_pptables_v1_0.c:933-934`
- fan present (`bNoFan == 0`, `ci_smumgr.c:2137`)
- `fan_table_start != 0` (`ci_smumgr.c:2143`)
- `duty100 != 0` (`ci_smumgr.c:2150`) — runtime register read

The attacker's only additional requirement is control of the VBIOS image —
the same trust boundary. Realistic vectors: malicious/faulty GPU ROM flash,
VFIO PCI passthrough of a card with a hacked VBIOS, supply-chain VBIOS
tampering. **Local DoS (kernel panic)** on driver attach / thermal setup.
Sibling of DF-1128 / DF-1204 / DF-1274 (same pattern in fiji/tonga/polaris
smumgr).

## Harness proof

`harness.c` replicates `ci_thermal_setup_fan_table`'s divisor block verbatim
and feeds it a crafted VBIOS with `usTMed == usTMin`. Output (decisive run):

```
DF-1297 ci_thermal_setup_fan_table divide-by-zero harness
VBIOS usTMin=2500 usTMed=2500 usTHigh=9000  duty100=100
=> t_diff1 = usTMed-usTMin = 0   (kernel divides by this)
SIGFPE caught: integer divide-by-zero on t_diff1 == 0
In-kernel equivalent: CPU trap 0 (#DE) -> kernel panic
RESULT: divide-by-zero CONFIRMED at ci_smumgr.c:2165
```

`-O0` is required for the harness: at `-O2` gcc treats integer div-by-zero as
undefined and elides the `div` instruction *when it can see the zero at
compile time*. The real kernel builds at `-O2`, but the divisor is read at
runtime from a parsed VBIOS struct pointer, so gcc cannot prove it is zero and
emits a real `div` → #DE trap. `-O0` faithfully reproduces that runtime
division in userspace.

## Build & run

```
cc -O0 -Wall -o harness harness.c   # -O0 REQUIRED (see note above)
./harness
```
or `./build.sh && ./run.sh`.

## Fix

`fix.diff` adds a guard after the deltas are computed, before the division:

```c
if (t_diff1 == 0 || t_diff2 == 0) {
    phm_cap_unset(hwmgr->platform_descriptor.platformCaps,
        PHM_PlatformCaps_MicrocodeFanControl);
    return -EINVAL;
}
```

This matches the existing pattern used elsewhere in the same function
(`ci_smumgr.c:2138-2141`, `:2144-2146`, `:2151-2153`) for the other
"disable microcode fan control and bail" conditions. It supersedes the
finding's one-line proposal by also disabling the capability (so the rest of
the powerplay stack does not keep trying to use the broken fan table).
