Divide-by-zero family in DCE8 display watermark math: dce_v8_0_latency_watermark divides by available_bandwidth and disp_clk before any guard (DF-3828 DCE11 / DF-3857 DCE10 twin)
Summary
dce_v8_0_latency_watermark() computes the three divides before its only guard (num_heads==0 - dead anyway via the caller gate). With DPM disabled, yclk/sclk derive from pm.current clocks seeded from unguarded VBIOS ulDefaultEngineClock/ulDefaultMemoryClock (assigned even on parse failure); one zero term collapses available_bandwidth to 0 and a mode set takes #DE -> kernel panic. VERIFIED: exact-replica harness on the guest traps SIGFPE 3/3, healthy-clock control returns normally. Unprivileged local DoS on any Bonaire/Kaveri/Kabini/Mullins in that state - one mode set panics at every mode change. DCE8 parts default to the legacy watermark path (DC not default for them), so this is the live path. Guard-reorder fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3878 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.9 KB | β raw | |
| VERDICT.md | β | 5.1 KB | β raw | |
| wm_dce8_div0.c | β | 5.9 KB | view raw | |
| build.sh | β | 191 B | view raw | |
| run.sh | β | 135 B | view raw | |
| build.log | β | 8 B | view raw | |
| run.log | β | 282 B | view raw | |
| run.2.log | β | 180 B | view raw | |
| dmesg.txt | β | 378 B | view raw | |
| env.txt | β | 531 B | view raw | |
| fix.diff | β | 2.5 KB | view raw | |
| verdict.json | β | 5.8 KB | view raw |
DF-3878 β Divide-by-zero family in DCE8 display watermark math (dce_v8_0_latency_watermark)
File: sys/dev/drm/amd/amdgpu/dce_v8_0.c β pass 2 (GLM 5.3) Twins: DF-3828 (DCE11, dce_v11_0.c:908-918), DF-3857 (DCE10, dce_v10_0.c:882-892), DF-3665 (evergreen family).
BUILD scp wm_dce8_div0.c dfbsd-maxx:/tmp/ vm.sh run_root 'cc -O2 -Wall -o /tmp/wm_dce8 /tmp/wm_dce8_div0.c'
RUN (unprivileged) vm.sh run_user '/tmp/wm_dce8; echo exit=$?' # affected: zero clocks vm.sh run_user '/tmp/wm_dce8 12000000 5000000 148500' # healthy control
EXPECTED healthy-clock control -> prints watermark value, no trap zero-clock (affected) -> SIGFPE (exit=136), dmesg "exited on signal 8" 3/3 runs trap; kernel path itself cannot run on this guest (0 AMD PCI devices, no /dev/dri β env.txt) β same hardware gate as DF-3857/3828.
WHAT IT PROVES Exact integer-level replica of dce_v8_0_available_bandwidth() (dce_v8_0.c:642-768) + dce_v8_0_latency_watermark() (:812-860): with DPM disabled and pm.current_mclk/current_sclk == 0 (VBIOS zero defaults, amdgpu_atombios.c:678/680 assigned unguarded at :718-719), available_bandwidth() collapses to 0 and the divides at :817-818 take the #DE trap β before the function's only guard (num_heads==0, :827, itself dead via the caller gate at :959).
WHICH WATERMARK CODE RUNS ON DCE8
CONFIG_DRM_AMD_DC is defined (sys/dev/drm/kconfig.h) but CIK takes DC
only when forced: BONAIRE/KAVERI/KABINI/MULLINS -> return amdgpu_dc > 0
(amdgpu_device.c:2304-2315) and amdgpu_dc defaults to -1, so the default
display path on those parts is this legacy file (dce_v8_1/8_2/8_3/8_5 ip
blocks, cik.c:2022-2081) β dce_v8_0_latency_watermark is live.
CHIP_HAWAII defaults to DC (amdgpu_dc != 0) and runs the DF-3869
dce_calcs family instead.
DF-3878 VERDICT β divide-by-zero in dce_v8_0_latency_watermark (DCE8)
Status: not_reproduced (kernel path) / arithmetic primitive reproduced 3/3 β source-certain. Same hardware gate as DF-3857 (DCE10) and DF-3828 (DCE11): this guest has no AMD ASIC (pciconf vendor 0x1002 count = 0, no /dev/dri β env.txt), so the kernel instruction cannot execute here. What is proven:
Root cause (path:line into this tree)
dce_v8_0_latency_watermark()divides byavailable_bandwidthat dce_v8_0.c:817 ((512*8*1000)/available_bandwidth) and :818 ((128*4*1000)/available_bandwidth), and bywm->disp_clkat :819 (40000000/wm->disp_clk) β all BEFORE the function's only guard (if (wm->num_heads == 0) return 0;, :827).- The num_heads guard is dead anyway: the sole caller chain
dce_v8_0_crtc_do_set_base:1973 βdce_v8_0_bandwidth_update:1082-1100 βdce_v8_0_program_watermarks:948 gates onamdgpu_crtc->base.enabled && num_heads && mode(:959). - Zero divisor traced end-to-end: with
!adev->pm.dpm_enabled(:967),wm_high.yclk = adev->pm.current_mclk * 10(:973) andwm_high.sclk = adev->pm.current_sclk * 10(:974).pm.current_mclk/current_sclkare seeded from VBIOSulDefaultMemoryClock/ulDefaultEngineClockwith NO zero-guard (amdgpu_atombios.c:678/680 β neighboring fields at :667/:694/:708 are clamped, these two are not) and are assigned EVEN ON PARSE FAILURE (amdgpu_atombios.c:718-719). One zero term collapsesdce_v8_0_available_bandwidth(:760-768, = min of :642/:700/:729 terms) to 0 β #DE β kernel panic. Identical seeding to DF-3828/3857; the atombios parser is asic-agnostic. - The
wm->disp_clk == 0vector (mode->clock) is KILLED in this tree:drm_mode_convert_umodeβdrm_mode_validate_driver(drm_modes.c:1745) βdrm_mode_validate_basic(drm_modes.c:1114) rejects clock==0 with MODE_CLOCK_LOW on the SETCRTC path; probed modes pass the same check in drm_probe_helper.c:368-375. - Second entry:
amdgpu_pm_compute_clocksβamdgpu_display_bandwidth_update(amdgpu_pm.c:2141) β same function on every DPM clock recompute.
Reproduction
wm_dce8_div0.cβ exact replica of the fixed20_12-truncated bandwidth math and the three divides. Affected inputs (zero clocks): SIGFPE, exit=136, 3/3 runs, dmesgpid NNNN (wm_dce8), uid 1001: exited on signal 8(run.log). Healthy-clock control (1200000/500000 kHz) returns 2297 without trapping.- In-kernel effect: the identical
divinstruction raises #DE in kernel mode β panic during an unprivileged DRM mode set on any Bonaire/Kaveri/ Kabini/Mullins in the zero-clock state β local DoS. No memory-safety impact (pure div0).
Pass-2 negatives checked and killed (dce_v10/v11 template)
- hpd arrays:
AMDGPU_HPD_NONE = 0xff(amdgpu_mode.h:92) so everyhpd >= num_hpdguard (:224, :248, :276, :318, :2942, :3133) rejects it; hpd_offsets[6] vs num_hpd=6 OK. - audio: pin[AMDGPU_MAX_AFMT_BLOCKS=9] (amdgpu_mode.h:261) vs num_pins<=7 (:1374-1383) OK; afmt[9] vs num_dig<=7 (KAVERI, cik.c:2023) OK; dig_offsets[7] covers it (:71-79).
- crtc: crtc_offsets[6] vs num_crtc<=6 (:385-405); vblank_get_counter
(:144-150) lacks a
crtc < 0check but every caller passes validated unsigned pipe (amdgpu_kms.c:1129-1156 via amdgpu_get_vblank_counter_kms which guardspipe >= num_crtc, amdgpu_kms.c:1109-1113); get_scanoutpos (:198-208) checks both bounds itself. - ISR indices:
dce_v8_0_crtc_irq(:3013-3018) indexes interrupt_status_offsets[6] with src_id-1 β bounded by the registration contract (sw_init :2645-2649 registers only 1..num_crtc; dispatch looks up client.sources[src_id], amdgpu_irq.c:426-431). Same for hpd_irq (:3126-3139, entry->src_data[0] checked at :3133 BEFORE use). - pageflip_irq
crtcs[crtc_id]read-before-check (:3082-3085): latent ordering defect (Linux moved the check first upstream), but crtc_id is derived from src_idβ{8..18} (registered :2651-2655) β in [0,5]; not attacker-reachable β hardening note only, consistent with the dce10 pass-2 treatment. - dfixed_const(wm->yclk) u32 overflow for large clocks (drm_fixed.h:35) degrades watermark math on ALL dce versions β correctness, not memory safety; shared drm_fixed.h, out of this file's scope.
- LUT loop (:2030-2035) reads 256 entries; gamma_size forced 256 (:2584 drm_mode_crtc_set_gamma_size(&base,256)) β exact.
- cursor_set2 (:2248-2331): GEM handle via drm_gem_object_lookup with file_priv; width/height upper-bounded (:2268-2272); width==0 causes only a harmless (u32)-1 register field. No priv issue.
Fix
Guard the divides (mirror of the num_heads guard) and zero-guard the VBIOS default-clock seeding β see fix.diff (git-apply-clean against sys/).
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 guards all three divides (moving the num_heads guard before them, adding disp_clk and available_bandwidth checks) plus the VBIOS zero-clock seeding, and is git-apply-clean against the read-only sys/ tree; a patched-kernel build cannot be exercised on this guest (no AMD ASIC, amdgpu never attaches), so hardware fix validation is not_testable here
fix.diff; VERDICT.md; env.txt (hardware gate)
Confirmed kernel references
- sys/dev/drm/amd/amdgpu/dce_v8_0.c:817
- sys/dev/drm/amd/amdgpu/dce_v8_0.c:818
- sys/dev/drm/amd/amdgpu/dce_v8_0.c:819
- sys/dev/drm/amd/amdgpu/dce_v8_0.c:827
- sys/dev/drm/amd/amdgpu/dce_v8_0.c:959
- sys/dev/drm/amd/amdgpu/dce_v8_0.c:973
- sys/dev/drm/amd/amdgpu/dce_v8_0.c:1973
- sys/dev/drm/amd/amdgpu/amdgpu_atombios.c:678
- sys/dev/drm/amd/amdgpu/amdgpu_atombios.c:718
- sys/dev/drm/amd/amdgpu/amdgpu_device.c:2315
- sys/dev/drm/drm_modes.c:1114
Detail
Exploit chain
unprivileged user with /dev/dri/cardX access -> drm_mode_setcrtc -> dce_v8_0_crtc_do_set_base (:1973) -> dce_v8_0_bandwidth_update (:1082) -> dce_v8_0_program_watermarks (:948, gate at :959 passes for any enabled crtc with num_heads>=1) -> dce_v8_0_latency_watermark -> (51281000)/available_bandwidth with available_bandwidth==0 -> #DE -> kernel panic. Precondition: pm.current_sclk or current_mclk == 0 (VBIOS zero default clocks or failed firmware-info parse with DPM off). Memory-safety impact: none (pure div0 DoS).
Evidence (decisive lines)
run.log (healthy control returns 2297, zero-clock input traps SIGFPE exit=136); run.2.log (3/3 stress traps); dmesg.txt ('pid NNNN (wm_dce8), uid 1001: exited on signal 8' 6x); env.txt (no AMD ASIC on guest); fix.diff (git-apply-clean); VERDICT.md (full chain + the dce10/11-template negative notes: hpd 0xff guards, audio pin[9]/num_pins<=7, afmt[9]/num_dig<=7, crtc_offsets[6], ISR src-id registration contract, pageflip read-before-check hardening note, LUT 256==gamma_size, cursor bounds)
PoC changes
Authored from scratch, modeled on the DF-3857 pack (no seed provided by orchestrator). Inputs via argv to defeat constant folding; dce_v8_0 line numbers and CIK dram-channel/bandwidth constants (dce_v8_0.c:642-768) adapted from this file; DC-vs-legacy boundary for DCE8 parts verified in-tree.
Verified recommended fix
Bail out of dce_v8_0_latency_watermark when num_heads/disp_clk/available_bandwidth is 0 (mirroring and moving the existing guard before the divides), and zero-guard ulDefaultEngineClock/ulDefaultMemoryClock seeding in amdgpu_atombios_get_clock_info
Verdict
Source-certain divide-by-zero in the DCE8 display watermark math, the direct DF-3828 (DCE11) / DF-3857 (DCE10) twin: dce_v8_0_latency_watermark() divides by available_bandwidth at dce_v8_0.c:817-818 and by wm->disp_clk at :819, all before the function's only guard (num_heads==0, :827, itself dead via the caller gate at :959) and with no divisor validation anywhere on the caller chain (mode set -> dce_v8_0_crtc_do_set_base :1973 -> dce_v8_0_bandwidth_update :1082 -> dce_v8_0_program_watermarks :948, plus the DPM recompute path amdgpu_pm.c:2141). Zero inputs traced end-to-end: with DPM disabled, wm.yclk/wm.sclk come from adev->pm.current_mclk/current_sclk (:973-974), seeded from VBIOS ulDefaultEngineClock/ulDefaultMemoryClock with no zero-guard (amdgpu_atombios.c:678/680) and assigned even on parse failure (:718-719) - identical to the DF-3828/3857 seeding; one zero term collapses dce_v8_0_available_bandwidth (:760-768) to 0 and the divide takes #DE -> kernel panic. The mode->clock==0 secondary vector is killed in this tree by drm_mode_validate_basic (drm_modes.c:1114) via drm_mode_convert_umode -> drm_mode_validate_driver (:1745). Which watermark function runs on DCE8 parts: verified that BONAIRE/KAVERI/KABINI/MULLINS take the legacy path by default (amdgpu_device.c:2304-2315 'return amdgpu_dc > 0' with amdgpu_dc defaulting to -1), so dce_v8_0_latency_watermark IS live on those parts; CHIP_HAWAII defaults to DC and runs the DF-3869 dce_calcs family instead. Arithmetic primitive proven on the guest: exact replica of the bandwidth/watermark math traps SIGFPE (exit=136, signal 8 = #DE, dmesg-confirmed) 3/3 stress runs while the healthy-clock control returns normally (run.log, run.2.log, dmesg.txt). Kernel-path reproduction impossible on this guest - 0 AMD PCI devices, no /dev/dri (env.txt), amdgpu never binds - hence not_reproduced for the kernel reach; impact ceiling on Bonaire/Kaveri/Kabini/Mullins hardware in the zero-clock state: local DoS via panic by an unprivileged DRM client mode set.
No comments yet.