# DF-1143 — divide-by-zero in `ci_thermal_setup_fan_table` (amdgpu)

## Verdict
**NOT REPRODUCED** on this guest (defense-in-depth source bug confirmed; path
unreachable on the default GENERIC DragonFlyBSD kernel running in QEMU).
Real source-level defect; fix.diff authored.

## Mechanism (source trace, bug confirmed real)
In `sys/dev/drm/amd/amdgpu/ci_dpm.c:1094`, `ci_thermal_setup_fan_table()`
computes the slope of the GPU fan PWM curve from three VBIOS-supplied u16
temperatures:

```c
/* sys/dev/drm/amd/amdgpu/ci_dpm.c:1122-1129 */
t_diff1 = adev->pm.dpm.fan.t_med - adev->pm.dpm.fan.t_min;   /* L1122 */
t_diff2 = adev->pm.dpm.fan.t_high - adev->pm.dpm.fan.t_med;  /* L1123 */
...
slope1 = (u16)((50 + ((16 * duty100 * pwm_diff1) / t_diff1)) / 100); /* L1128 */
slope2 = (u16)((50 + ((16 * duty100 * pwm_diff2) / t_diff2)) / 100); /* L1129 */
```

`t_diff1` and `t_diff2` are integer divisors with **no zero-check**.  Both
temperatures are populated directly from the AtomBIOS PowerPlay table in
`sys/dev/drm/amd/amdgpu/amdgpu_dpm.c:360-362`:

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

If a (potentially malicious) VBIOS image encodes `usTMin == usTMed` or
`usTMed == usTHigh`, the kernel executes an integer divide-by-zero at L1128
or L1129 → x86 `#DE` → kernel panic during DPM enable (every boot/resume on
the affected card).

## Why it does NOT reproduce on this guest
1. **Driver is not in the kernel.** `grep -E 'radeon|amdgpu' sys/config/X86_64_GENERIC`
   returns nothing; both drivers ship as loadable modules only:
   ```
   $ ssh dfbsd 'ls /boot/kernel/ | grep -E "amdgpu|radeon" | head -3'
   amdgpu.ko
   radeon.ko
   ```
   `nm /boot/kernel/kernel.debug | grep -ciE 'radeon|amdgpu'` ⇒ **0**.  The
   running `#0` GENERIC kernel has zero radeon/amdgpu symbols compiled in,
   and no amdgpu module is loaded (`kldstat | grep amdgpu` is empty).
2. **No AMD GPU hardware in the QEMU guest.** The guest is a serial-console
   VM (`vtnet0`/`vtblk0`, no GPU passthrough); there is no AMD Radeon device
   on the virtual PCI bus, so even with the module loaded the driver's
   `probe()` would never bind and the VBIOS parser at `amdgpu_dpm.c:348`
   would never execute.
3. **Threat model is malicious-VBIOS, not unprivileged local.** Even on real
   AMD hardware, the trigger is a malformed AtomBIOS image — the attacker
   model is "reflashed VBIOS / VFIO PCIe passthrough of a hostile GPU," not
   an unprivileged local user issuing syscalls.  No `uid=0` chain exists
   from this primitive on a default kernel.

## Exploit chain
none — the primitive (kernel #DE / DoS) is gated behind AMD hardware +
module load + attacker-controlled VBIOS, none of which exist on this guest.

## PoC changes
none.  No PoC source is provided because the path is unreachable on this
kernel/guest combination (no driver in kernel, no hardware, attacker class
is malicious-VBIOS not unprivileged-local).  Reproducibility would require
either (a) a physical AMD Bonaire/Hawaii/Kabini GPU with a crafted VBIOS
image, or (b) VFIO PCIe passthrough of such a GPU into a VM with the
amdgpu module loaded.

## Recommended fix
Author a guard before the divisions: if either delta is zero (or negative
in u16 wrap), disable ucode fan control and return early, mirroring the
existing `duty100 == 0` early-return at L1113.  See `fix.diff` — applies
cleanly with `git apply` (validated).  This matches the pattern of the
upstream Linux hardening and the finding markdown's proposal.
