# DF-3869 — Zero-divisor (#DE panic) family in dce_calcs calculate_bandwidth()

**File:** `sys/dev/drm/amd/display/dc/calcs/dce_calcs.c`
**Sinks:** line 839 (`horizontal_blank_and_chunk_granularity_factor` outer `bw_div`) and line 1945 (`num_stutter_bursts` u32 `/`)
**Family:** DF-3665 (evergreen watermark div0) / DF-3828 / DF-3857 — same class, DCE display-core bandwidth calculator.
**Severity:** Medium (local DoS, kernel panic) — hardware-gated to DCE110/DCE112 AMD DC parts (Carrizo/Stoney/Polaris/Vega10 with `amdgpu.dc=1`).

## Build & run (works on any UNIX; verified on the audit guest)

```
cc -O2 -o dce839_zero_div dce839_zero_div.c
./dce839_zero_div
```

## Expected output

The harness replays, with kernel-verbatim fixed-point code (copied from
`bw_fixed.c`, `bw_fixed.h`, `fixpt31_32.c`, `fixed31_32.h`, `dc_resource.c`),
the exact attacker input chain for an unprivileged-supplied DRM mode blob
(1x16, htotal=3, clock=25 MHz) and plane state (FB 128x4, src 128x4, dst 2x3):

1. the mode passes `drm_mode_validate_basic()` (drm_modes.c:1103) — nothing in
   the DRM core forbids htotal=3/hdisplay=1;
2. `resource_build_scaling_params` succeeds (viewport 64x21 ≥ 16), so
   `bw_calcs()` (dce_calcs.c:2985) runs `populate_initial_data` +
   `calculate_bandwidth`;
3. hsr = 64 (exact in 24.24), vsr ≤ 4/3 → the branch guards at dce_calcs.c:804/820
   hold and **line 839 executes**;
4. inner sum `h_total + (src_width−256)/hsr` = 3 + (64−256)/64 = **exactly 0**
   → the outer `bw_div(h_total, 0)` divisor is **0**;
5. in-kernel this reaches `bw_frc_to_fixed(n, 0)` (bw_fixed.c:57) whose
   `ASSERT(denominator != 0)` is compiled to `WARN_ON` in release builds
   (os_types.h:76), then `div64_u64_rem(n, 0, …)` (math64.h:76, plain C `%`)
   → x86-64 `divq` with divisor 0 → **#DE → trap_fatal → kernel panic**.

The harness children perform the same divisions in userland and die with
SIGFPE (the #DE analog). Guest stays up: this is a userland arithmetic proof;
the kernel path itself needs DCE hardware the guest does not have.

## Secondary sink (mechanism-demonstrated, not end-to-end proven)

dce_calcs.c:1939 stores `bw_fixed_to_int(stutter_dmif_buffer_size[i])` into
`uint32_t min_stutter_dmif_buffer_size` (dce_calcs.h:359); any value in (0,1)
truncates to 0 and line 1945 `total_bytes_requested / min_stutter_dmif_buffer_size`
(u32/u32 → `divl` #0 → #DE panic). Same degenerate-mode family; the full
formula chain was not replayed end-to-end, so this sink is `likely` rather than
`certain`.

## Fixes

`fix.diff` in this directory guards both sinks (and hardens `bw_frc_to_fixed`).
