# DF-4582 VERDICT

**Finding**: `rs690_crtc_bandwidth_compute()` divide-by-zero via
`dfixed_const(mode->clock)` 32-bit shift wrap; local user with DRM access on
RS690/RS740/RS780/RS880 hardware turns one `DRM_IOCTL_MODE_SETCRTC` into a
kernel panic (integer divide fault, trap 18).

## Status: primitive reproduced on guest (kernel + userland + shipped
binary); end-to-end ioctl chain NOT executed (no AMD IGP in the QEMU
guest).  Overall: **inconclusive** for the full chain, `confidence:
likely` — every hop of the chain is individually proven (see below), only
the hardware hop is missing.

## The bug, line by line

* `sys/dev/drm/radeon/rs690.c:326-329`
  ```c
  a.full = dfixed_const(mode->clock);   /* (u32)(clock << 12) */
  b.full = dfixed_const(1000);
  a.full = dfixed_div(a, b);            /* MHz-ish; == 0 when wrapped */
  pclk.full = dfixed_div(b, a);         /* divisor == 0 -> #DE */
  ```
* `sys/dev/drm/include/drm/drm_fixed.h:35` `dfixed_const(A)` = `(u32)((A)<<12)`
  — clock = 1048576 (2^20 kHz) ⇒ `2^20 << 12 = 2^32 ≡ 0 (mod 2^32)`.
* `sys/dev/drm/include/drm/drm_fixed.h:62-70` `dfixed_div` →
  `do_div(tmp, B.full)` → `sys/dev/drm/include/linux/math64.h:36-42`
  `__rem = n % __base; n /= __base` — `__base==0` ⇒ `divq` by zero.

## Reachability chain (each hop verified in this tree)

1. Local user opens /dev/dri/cardN ⇒ becomes DRM master (MODE_SETCRTC is
   `DRM_MASTER|DRM_UNLOCKED`, drm_ioctl.c:656).
2. `drm_mode_setcrtc` (drm_crtc.c:655) → `drm_mode_convert_umode`
   (drm_modes.c:1691): only `clock > INT_MAX` rejected (:1696); our 1048576
   passes; `drm_mode_validate_driver` (:1145) → `drm_mode_validate_basic`
   (:1106) rejects only `clock == 0` (:1114) — 1048576 passes; radeon
   installs no `mode_config.funcs->mode_valid` (radeon_display.c:1350-1353),
   so no range check exists.  `drm_crtc_check_viewport` only checks fb
   geometry.
3. Mode applied by crtc helpers (encoder mode_fixups in radeon are
   unconditional `return true`, e.g. atombios_encoders.c:356); PLL
   programming is best-effort and does not fail the modeset.
4. crtc enable → `radeon_pm_compute_clocks` (atombios_crtc.c:296) →
   `radeon_pm_compute_clocks_old` (radeon_pm.c:1729): PROFILE method calls
   `radeon_pm_set_clocks` unconditionally (:1753-1755).
5. `radeon_pm_set_clocks` → `radeon_bandwidth_update` (radeon_pm.c:311-312,
   guarded only by `active_crtc_count != 0` — true after the modeset).
6. `rs690_bandwidth_update` (rs690.c:583) → `rs690_crtc_bandwidth_compute`
   (rs690.c:625) with `crtc->base.mode` = the crafted mode → #DE.

ASICs: `.bandwidth_update = &rs690_bandwidth_update` for RS690/RS740
(radeon_asic.c:727) and RS780/RS880 (radeon_asic.c:1138).

## What was executed on the guest

| run | result |
|---|---|
| `./rs690_div0` (userland, -O0, verbatim math) | SIGFPE ×3 scenarios |
| `./rs690_div0` (userland, -O2) | survived — gcc UB-fused the div chain in *this* build context (documented; not the source semantics, not what the shipped binary does) |
| KLD `sysctl df4582.run=0` (kernel flags, runtime value) | **Fatal trap 18: integer divide fault, `dfixed_div+0xa: divq`**, guest down (panic.txt) |
| `objdump /boot/kernel/radeon.ko` | shipped binary contains the runtime div with the truncating `shl $0xc` at 0x5ac39-0x5ac61 ⇒ trap reachable in the *shipped* code, no UB folding |
| KLD WITH_FIX, scenarios 0/1/2 | `DF4582-FIX: zero divisor avoided`, all survive, clean unload (fix_run.log) |

The harness `dfixed_div` is marked `noinline` in the KERNEL build so the
compiler cannot fuse the inner (by-constant) and outer divisions — the
shipped radeon.ko build happens not to fuse them either (the disassembly
proves a real `div` by the runtime divisor); `noinline` reproduces exactly
that codegen shape without changing any arithmetic.

## Why not "reproduced" overall

The DRM ioctl → modeset → bandwidth-update chain requires an RS690/RS740/
RS780/RS880 IGP; the audit guest's only GPU is virtio-vga and /dev/dri does
not exist.  The panic primitive, the zero-divisor arithmetic, the missing
validation, and the trigger plumbing are all individually demonstrated or
source-proven; the single missing hop is the hardware.  Hence
`status: inconclusive`, `reproduced: 0`, `impact: panic`, `confidence:
likely` (certain for the primitive).

## Fix validation

`fix.diff` adds a zero-divisor guard after the clock conversion (bail with
the same safe watermark the disabled-CRTC path uses, rs690.c:283-287).
Validated at harness level: baseline kernel-flag build panics (trap 18);
WITH_FIX build survives all scenarios and unloads cleanly.  A full
hardware validation would need an RS690-class board.

## Exploitability ceiling

DoS (kernel panic) — no memory corruption; the write set of the bug is the
divide fault only.  On multi-user RS780-era machines (780G/785G chipsets
were mass-market) with /dev/dri group access this is a one-syscall local
kernel panic from any unprivileged user who can open the card.

## 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.
