# DF-3665 VERDICT — divide-by-zero in `evergreen_latency_watermark()`

## Reproduced? (what "reproduced" means here)

**The arithmetic root cause and the divide-by-zero itself are fully proven; the kernel
path cannot be executed on this audit guest because it has no radeon hardware.** The
guest is virtio-only (`env.txt`: `vgapci0 ... chip=0x11111234` virtio-gpu, no
`/dev/dri`, no drm kmods loaded), so `sys/dev/drm/radeon/evergreen.c` code paths are
unreachable in the running kernel. Per the audit convention for hardware-absent
findings, the machine verdict is `inconclusive` (reproduced=0) with source-level
certainty documented below.

## The proof chain (all `path:line` verified against this tree)

1. **The sink.** `sys/dev/drm/radeon/evergreen.c:2048-2049`:
   `(512 * 8 * 1000) / available_bandwidth` and `(128 * 4 * 1000) / available_bandwidth`
   are unconditional u32 divisions executed at the top of
   `evergreen_latency_watermark()` — the `num_heads == 0` early-return sits at
   `evergreen.c:2057`, *after* the divisions.

2. **Call chain to the sink.** Any mode set on an enabled CRTC ends in
   `atombios_crtc.c:1448` (`radeon_bandwidth_update(rdev)` inside
   `evergreen_crtc_do_set_base`) → `evergreen.c:2303 evergreen_bandwidth_update()` →
   `evergreen.c:2323 evergreen_program_watermarks()` → `evergreen.c:2216/2218`
   `evergreen_latency_watermark(&wm_high/&wm_low)`. PM clock changes reach the same
   code via `radeon_pm.c:312/1093/1112/1142`.

3. **`available_bandwidth` can be zero — three ways** (verbatim-math simulation in
   `df3665.c`, executed on the guest; see `run.log`):

   a. **User-controlled mode clock 1..38 kHz.** `radeon_display.c:1350`
      `radeon_mode_funcs` has **no** `.mode_valid`, so `drm_mode_convert_umode()`
      (`drm_modes.c`, calls `drm_mode_validate_driver` → `drm_mode_validate_basic`
      at `drm_modes.c:1103-1130`) only rejects `clock == 0` — 25 kHz passes.
      `wm.disp_clk = mode->clock` (`evergreen.c:2172`) →
      `evergreen_dmif_request_bandwidth()` (`evergreen.c:1989-2007`) computes
      `trunc(32 * (25/1000) * 0.8) = 0` → `min()` at `evergreen.c:2016` → 0.
   b. **u32 fixed-point wrap.** `drm_fixed.h:32-42`: `fixed20_12` is a **u32**;
      `dfixed_const(A) = (u32)(A << 12)`. Inputs are `wm->yclk = mclk*10` /
      `wm->sclk = sclk*10` (`evergreen.c:2163-2169`, `2190-2196`), so any clock
      ≥ 104,857.6 kHz wraps. Exhaustive scan (0..4 GHz, step 1 kHz, u32 semantics):
      **351 mclk values** zero `evergreen_dram_bandwidth()` — including exactly
      524288, 1048576, 1572864 kHz and the k·2^20/10 wrap bands (419431 kHz shown)
      — and **156 sclk values** zero `evergreen_data_return_bandwidth()`. These
      clocks are VBIOS/DPM-controlled (`rdev->pm.current_mclk/sclk`,
      `radeon_dpm_get_mclk/sclk`).
   c. **VBIOS zero clocks** (FirmwareInfo reporting mclk/sclk = 0, the DF-1708
      class) trivially zero the bandwidth.

4. **The trap.** In-kernel, `u32 / 0` on x86-64 raises #DE → kernel panic. The PoC
   executes the exact division (divisor fed through a pipe so the compiler cannot
   fold it) in forked children: **9/9 divisor-zero scenarios died with SIGFPE**
   (`run.log`), the userspace manifestation of the same fault. The three control
   scenarios (typical HD5870/HD5770/sumo clocks) returned 2851/1900/487 — nonzero,
   no trap — demonstrating the boundary is exactly where the math says it is
   (clock = 39 kHz → 1; clock = 38 kHz → 0).

## Why not "reproduced"/panic on the guest

The guest kernel would panic at `evergreen.c:2048` on the first `bandwidth_update`
after such a mode set — but there is no Evergreen GPU in the QEMU guest (virtio
display only), so `/dev/dri/radeon` cannot exist and the ioctl path cannot run.
Building a patched kernel to "validate the fix" is likewise not testable without the
device; `fix.diff` is validated by inspection (it moves the divisions behind a
zero-guard, mirroring the arithmetic the PoC demonstrates).

## Threat model

- Local user with DRM-master rights (console user, X session, or any local user when
  no other master holds the device) submits a `DRM_IOCTL_MODE_SETCRTC` user mode with
  clock ≤ 38 kHz → **immediate kernel panic** (path 3a needs no unusual hardware
  state at all beyond an Evergreen-class radeon).
- Conversely, unlucky-but-realistic VBIOS/DPM memory or engine clock values
  (524.288 MHz, 1048.576 MHz, ...) panic on the *first* mode set at boot or on the
  first DPM level change (paths 3b/3c).

Impact: reliable local denial of service (kernel panic). No memory corruption, no
privilege escalation. Severity Medium, consistent with the audit's precedent for
VBIOS-divisor panics (DF-1386, DF-1650, DF-1137).

## Fix

Guard the divisor (and while there, guard `wm->disp_clk` for line 2050) — see
`fix.diff`:

```diff
 	u32 mc_latency = 2000; /* 2000 ns. */
 	u32 available_bandwidth = evergreen_available_bandwidth(wm);
+	if (!available_bandwidth || !wm->disp_clk)
+		return 0;
 	u32 worst_chunk_return_time = (512 * 8 * 1000) / available_bandwidth;
```

Returning 0 makes the watermark "no latency hiding required", which is the safe
fallback the function already uses for `num_heads == 0`. A deeper fix (out of scope
for a minimal security patch) would keep the fixed-point math in 64-bit so the
`dfixed_const()` wrap stops corrupting every watermark above 1 GHz inputs.

## Cross-references

- Same-family audit findings: DF-1386 (cypress burst time), DF-1650 (rs780
  drm_mode_vrefresh), DF-1431 (radeon pll sub-10 kHz clock).
- The u32 fixed20_12 wrap additionally makes all DCE4/5 watermark *values* garbage
  for mclk ≥ 104.86 MHz — a correctness issue upstream shares; only the zero-window
  values are security-relevant.

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
