Zero-divisor (#DE kernel panic) in calculate_bandwidth: exact-zero divisor at dce_calcs.c:839 from UABI-legal degenerate modes, plus u32 division-by-zero at :1945
Summary
bw_div's ASSERT(denominator!=0) compiles to WARN_ON in release and bw_frc_to_fixed then executes div64_u64_rem(n,0) -> x86-64 divq #0 -> panic. An unprivileged user-mode DRM mode blob (1x16, htotal=3, clock=25MHz) plus plane (src 128x4 -> dst 2x3) passes drm_mode_validate_basic, satisfies the viewport>=16 gate, and makes the :839 divisor EXACTLY 0 (3 + (64-256)/64 = 0, all integers exact in 24.24 fixed point). Secondary sink: min_stutter_dmif_buffer_size (u32) truncates from fixed-point in (0,1) to 0 and :1945 divides by it. VERIFIED: kernel-verbatim fixed-point PoC on the guest - 'DIVISOR == 0 CONFIRMED' + two SIGFPE children. Local unprivileged DoS (kernel panic) on any DCE110/DCE112 DC part (Carrizo/Stoney/Polaris/Vega10 with amdgpu DC enabled): one atomic modeset with a crafted mode blob panics during dc_validate_global_state. Guard + hardened bw_frc_to_fixed.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3869 · 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | — | 2.6 KB | ↓ raw | |
| VERDICT.md | — | 4.2 KB | ↓ raw | |
| dce839_zero_div.c | — | 11.5 KB | view raw | |
| build.sh | — | 98 B | view raw | |
| run.sh | — | 98 B | view raw | |
| run.log | — | 1.1 KB | view raw | |
| run.1.log | — | 1.1 KB | view raw | |
| fix.diff | — | 2.7 KB | view raw | |
| manifest.json | — | 1.1 KB | view raw | |
| verdict.json | — | 2.3 KB | view raw |
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):
- the mode passes
drm_mode_validate_basic()(drm_modes.c:1103) — nothing in the DRM core forbids htotal=3/hdisplay=1; resource_build_scaling_paramssucceeds (viewport 64x21 ≥ 16), sobw_calcs()(dce_calcs.c:2985) runspopulate_initial_data+calculate_bandwidth;- hsr = 64 (exact in 24.24), vsr ≤ 4/3 → the branch guards at dce_calcs.c:804/820 hold and line 839 executes;
- inner sum
h_total + (src_width−256)/hsr= 3 + (64−256)/64 = exactly 0 → the outerbw_div(h_total, 0)divisor is 0; - in-kernel this reaches
bw_frc_to_fixed(n, 0)(bw_fixed.c:57) whoseASSERT(denominator != 0)is compiled toWARN_ONin release builds (os_types.h:76), thendiv64_u64_rem(n, 0, …)(math64.h:76, plain C%) → x86-64divqwith 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).
DF-3869 VERDICT
Status: inconclusive (hardware-gated) — the guest has no AMD GPU (virtio-only
QEMU), so the in-kernel path cannot be executed here. The arithmetic that
produces the zero divisor was reproduced exactly, on the guest, with
kernel-verbatim fixed-point code (run.log).
What was proven (run.log, guest uname in file)
bw_calcs()is reachable from unprivileged mode-set on DCE110/112 DC: DRM_IOCTL_MODE_ATOMIC with a user mode blob →drm_atomic_set_mode_prop_for_crtc→drm_mode_convert_umode→drm_mode_validate_basic(drm_modes.c:1103-1120: only clock≠0 and hdisplay≤hsync_start≤hsync_end≤htotal) → amdgpu_dmfill_stream_properties_from_drm_display_mode(amdgpu_dm.c:2678,2689 —h_total = crtc_htotal,pix_clk_khz = crtc_clock, no zero/degeneracy checks) →dc_validate_global_state(dc_resource.c:2079) →resource_build_scaling_params_for_context(dc_resource.c:2133) →dce110_validate_bandwidth/dce112(dce110_resource.c:857-870) →bw_calcs(dce_calcs.c:2985).- Constructed input: mode 1x16 htotal=3 clock=25 MHz; plane src 128x4 → dst 2x3. All DRM-core checks pass (harness step [1]).
- viewport 64x21 ≥ 16 →
resource_build_scaling_paramsreturns true (dc_resource.c:1125) sobw_calcsis reached (harness step [2]). - hsr = 64 exactly (24.24 word 1073741824), vsr = 22369600 ≤ frc(4/3)
(harness step [3]); branch guards at dce_calcs.c:804 (vsr>1 → prefetch=0)
and :817-834 (lb_lines_mid = 4/3 ∉ {2,4}) hold → the
elseat :838 runs. - dce_calcs.c:839 divisor =
bw_div(h_total + (src_width−256)/hsr, 2)= bw_div(3<<24 + (−3<<24), 2) = 0 EXACTLY (harness steps [5][6]). - In-kernel consequence:
bw_div(x, 0)→bw_frc_to_fixed(x, 0)(bw_fixed.h:121-124, bw_fixed.c:57):ASSERT(denominator != 0)is onlyWARN_ONin release (os_types.h:72-77), thendiv64_u64_rem(n, 0, &rem)(math64.h:76-80, plain C%) → CPU #DE in kernel → trap_fatal → panic. Userland analogs both fired SIGFPE (harness steps [7][9]).
Why the other big divisors are NOT zero (negative results with weight)
pixel_rate/h_totalcan never be 0: drm coredrm_mode_validate_basicrejects clock==0 (MODE_CLOCK_LOW) and hdisplay==0 with htotal≥hsync_end (drm_modes.c:1114-1120);pix_clk_khz*=2for frame packing keeps ≠0.hsr/vsrcan never be 0 on the reachable path: amdgpu_dmfill_rects_from_plane_staterejects zero src/dst extents (amdgpu_dm.c:2439-2459);calculate_scaling_ratiostruncates ratios to 19 fractional bits (dc_resource.c:747-753) which could zero a ratio < 2^-19, but theviewport ≥ 16gate (dc_resource.c:1125) with clip ≤ 16384 bounds ratio ≥ 16/16384 = 2^-10 > 2^-19. Killed.- OOB in
populate_initial_data: max write index = num_displays+4 ≤ 9 (≤6 streams) <maximum_number_of_surfaces= 12 (dce_calcs.h:233); watermark[9] reads in-bounds. Killed. - No VBIOS mem_latency/num_states walk exists in this file (that is
dcn_calcs.c); all vbios/dceip data here are compile-time constants except
asic_id.vram_width(→ DF-3870). - No recursion; single kzalloc'd
bw_calcs_data; deterministic.
Sink 1945 (secondary, likely not certain)
min_stutter_dmif_buffer_size (u32, dce_calcs.h:359) is set at :1939 via
truncating bw_fixed_to_int(); a stutter_dmif_buffer_size in (0,1) yields 0
and :1945 divides u32/u32 → divl #0 → #DE. Degenerate timings shrink the
product below 1; full-chain emulation not performed (the formula depends on
the entire watermark model), so recorded as likely, same root cause family.
Bottom line
Source-certain: an unprivileged user with a DRM master/_modeset on a DCE
display can drive calculate_bandwidth() into an exact-integer zero divisor
at dce_calcs.c:839 → kernel #DE panic. Hardware-gated (no AMD GPU on guest),
hence inconclusive runtime verdict; arithmetic proven exactly on the guest.
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.
Fix verification
not_testablefix.diff authored against read-only sys/; cannot build DCE kernel path on guest (no AMD HW)
fix.diff
Confirmed kernel references
- sys/dev/drm/amd/display/dc/calcs/dce_calcs.c:839
- sys/dev/drm/amd/display/dc/calcs/dce_calcs.c:1945
- sys/dev/drm/amd/display/dc/calcs/bw_fixed.c:57
- sys/dev/drm/amd/display/dc/inc/bw_fixed.h:121
- sys/dev/drm/amd/display/dc/os_types.h:76
- sys/dev/drm/include/linux/math64.h:76
- sys/dev/drm/drm_modes.c:1103
- sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm.c:2678
Detail
Evidence (decisive lines)
run.log steps [5][6] divisor==0; [7]/[9] SIGFPE children; VERDICT.md reachability chain and killed negatives
PoC changes
harness written fresh (no seed): verbatim copies of bw_frc_to_fixed/dc_fixpt_truncate/fixed31_32_to_bw_fixed + the line-839 expression; fixed DCE of the faulting divisions by storing into volatile
Verified recommended fix
Guard zero denominators at dce_calcs.c:839 and :1945; harden bw_frc_to_fixed against divisor 0 (see fix.diff)
Verdict
Hardware-gated (guest has no AMD GPU): in-kernel path not executable here, but the zero divisor at dce_calcs.c:839 was proven EXACTLY on the guest with kernel-verbatim fixed-point arithmetic (run.log: divisor==0, both #DE analogs SIGFPE). An unprivileged DRM mode blob (1x16 htotal=3) + plane (src 128x4 -> dst 2x3) passes drm_mode_validate_basic, reaches bw_calcs on DCE110/112, and makes the line-839 outer bw_div divide by exactly 0 -> div64_u64_rem(n,0) -> kernel #DE panic. Secondary sink 1945 (u32 truncation -> 0 -> divl) is likely, same family.
No comments yet.