Divide-by-zero in dce_v10_0_latency_watermark: divides by available_bandwidth/disp_clk before any guard (DF-3828 DCE11 twin)
Summary
dce_v10_0_latency_watermark() computes the three divides before the function's only guard (num_heads==0, itself dead via the caller gate) and with no divisor validation on any caller path. When the memory/engine clocks read as zero, available_bandwidth() collapses to 0 and a mode-set takes an integer #DE -> panic. Clocks are seeded from VBIOS ulDefaultEngineClock/ulDefaultMemoryClock with no zero-guard and assigned even on parse failure - the exact seeding DF-3828 proved for DCE11 (asic-agnostic parser, applies to Tonga/Fiji). Arithmetic primitive VERIFIED on guest: exact replica traps SIGFPE 3/3 runs, healthy-clock control returns normally. Local unprivileged DoS (kernel panic) on any Tonga/Fiji machine whose default clocks read as 0: an unprivileged DRM client issues a mode set and the kernel takes #DE.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3857 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.9 KB | β raw | |
| VERDICT.md | β | 5.3 KB | β raw | |
| wm_div0.c | β | 5.1 KB | view raw | |
| build.sh | β | 115 B | view raw | |
| run.sh | β | 124 B | view raw | |
| build.log | β | 184 B | view raw | |
| run.log | β | 586 B | view raw | |
| run.2.log | β | 608 B | view raw | |
| env.txt | β | 467 B | view raw | |
| fix.diff | β | 1.6 KB | view raw | |
| verdict.json | β | 4.7 KB | view raw | |
| manifest.json | β | 1.3 KB | view raw |
DF-3857 β Divide-by-zero in dce_v10_0_latency_watermark (DCE10, DF-3828 twin)
What
sys/dev/drm/amd/amdgpu/dce_v10_0.c:882-884 β dce_v10_0_latency_watermark()
computes
u32 worst_chunk_return_time = (512 * 8 * 1000) / available_bandwidth; /* :882 */
u32 cursor_line_pair_return_time = (128 * 4 * 1000) / available_bandwidth; /* :883 */
u32 dc_latency = 40000000 / wm->disp_clk; /* :884 */
all three before the function's only guard (if (wm->num_heads == 0)
return 0;, :892). When available_bandwidth collapses to 0 the first
divide takes an integer #DE in kernel context β panic. The num_heads==0
guard is additionally dead through this file's only caller:
dce_v10_0_program_watermarks() gates the call on
amdgpu_crtc->base.enabled && num_heads && mode (:1024).
Zero-clock source: with DPM disabled, wm.yclk/wm.sclk =
adev->pm.current_mclk/current_sclk * 10 (:1038-1039, :1077-1078);
pm.current_* are seeded from the VBIOS ulDefaultEngineClock/
ulDefaultMemoryClock with no zero-guard (amdgpu_atombios.c:677-680) and are
assigned even when the firmware-info parse fails (:718-719) β the exact
seeding DF-3828 proved for DCE11; the parser is asic-agnostic (Tonga/Fiji).
With yclk == 0 (or sclk == 0) the min() in
dce_v10_0_available_bandwidth() (:825-833) is 0.
The mode->clock == 0 secondary vector is already blocked at the DRM layer
in this tree (drm_mode_validate_basic(), sys/dev/drm/drm_modes.c:1114
returns MODE_CLOCK_LOW), so wm->disp_clk != 0 for any mode that reaches
program_watermarks β the live vector is the zero memory/engine clock.
Reproduce (arithmetic-level; kernel reach is hardware-gated)
build.sh # cc -O2 -Wall -o /tmp/wm_div0 /tmp/wm_div0.c run.sh # /tmp/wm_div0; echo exit=$?
Expected (observed on the audit guest, run.log):
healthy clocks: watermark math returns 2297 (no trap) affected clocks (current_mclk=0, current_sclk=0): entering latency_watermark()... exit=136 # 128+8 -> SIGFPE = #DE, the kernel panic class
Control (/tmp/wm_div0 6000000 3000000 148500) returns normally.
run.2.log: dmesg shows pid NNNN (wm_div0), uid 0: exited on signal 8
3/3 runs.
Impact
Local DoS (kernel panic) on any Tonga/Fiji machine whose default clocks read
as 0 (crafted/blank VBIOS or failed firmware-info parse with DPM off):
an unprivileged DRM client issues a mode set β
dce_v10_0_crtc_do_set_base β dce_v10_0_bandwidth_update (:2064) β
program_watermarks β latency_watermark β #DE β panic.
Kernel reach on this guest
Not possible: the audit guest has 0 AMD PCI devices (pciconf shows no
vendor=0x1002), no /dev/dri (env.txt) β amdgpu never binds. Hence
not_reproduced for the kernel path; the arithmetic primitive and the full
call chain are proven at source level and the trap class is demonstrated.
DF-3857 VERDICT β dce_v10_0_latency_watermark divide-by-zero (DF-3828 twin)
Status: not_reproduced (kernel reach hardware-gated) β arithmetic primitive proven on guest, source chain certain.
Root cause (path:line, all in sys/dev/drm/amd/amdgpu/ unless noted)
dce_v10_0_latency_watermark() divides by available_bandwidth at
dce_v10_0.c:882-883 and by wm->disp_clk at :884, before the only guard
if (wm->num_heads == 0) return 0; at :892, with no divisor validation on
any caller path:
dce_v10_0_crtc_do_set_base():2064βdce_v10_0_bandwidth_update():1145-1163βdce_v10_0_program_watermarks():1013-1135βdce_v10_0_latency_watermark():877. Triggered by any mode set (unprivileged DRM master viaDRM_IOCTL_MODE_SETCRTC/ page-flip base update) and byamdgpu_pm_compute_clocksDPMS transitions (:2510).dce_v10_0_program_watermarks:1032-1040/:1071-1079: withadev->pm.dpm_enabled == false,wm.yclk = pm.current_mclk * 10,wm.sclk = pm.current_sclk * 10.amdgpu_atombios_get_clock_info()(amdgpu_atombios.c:677-680) copies VBIOSulDefaultEngineClock/ulDefaultMemoryClockintoadev->clock.default_sclk/default_mclkwith no zero-guard (every neighbouring field does get one β see DF-3828's audit), and:718-719assigns them topm.current_sclk/current_mclkeven when the parse fails.dce_v10_0_available_bandwidth():825-833=min(dram_bw, data_ret, dmif_req);dram_bw = trunc(ch*4 * yclk/1000 * 0.7)(:707-725) is 0 whenyclk==0;data_retis 0 whensclk==0(:765-783). One zero term zeroes themin.:882(512*8*1000) / 0β integer #DE β kernel panic.
Guard placement: the num_heads==0 early-return at :892 is dead through
this file's only caller (gate at :1024 includes num_heads), so even a
perfect num_heads guard could not have prevented the zero-bandwidth divide β
the missing check is on the divisors, exactly the DF-3828/DF-3665 family
defect.
Secondary vectors examined and killed:
- mode->clock == 0 (div_u64 divides at :1025-1027, wm->disp_clk at
:884): blocked pre-driver by drm_mode_validate_basic()
(sys/dev/drm/drm_modes.c:1114-1115, MODE_CLOCK_LOW) in the
drm_mode_setcrtc/drm_mode_convert_umode path (drm_crtc.c:655,
drm_modes.c:1750). No user-supplied 0-clock mode reaches DCE10.
- wm->src_width == 0 divide in dce_v10_0_check_latency_hiding() :978:
same validation rejects hdisplay==0 (drm_modes.c:1117).
- HPD/audio/crtc array bounds (dce_v11 template): hpd_offsets[6] uses are
guarded (:279, :303, :331, :383, :3010, :3162);
interrupt_status_offsets[6] / crtc_offsets[7] uses
(:3206-3207, :3113-3114) are bounded by the amdgpu_irq dispatch
contract (only client/src_id pairs registered via amdgpu_irq_add_id
reach .process; amdgpu_irq.c:356, :414-432); num_pins=7/num_dig=7
fit pin[AMDGPU_MAX_AFMT_BLOCKS=9]/afmt[9] (amdgpu_mode.h:261,329);
crtc_status[6] (:403) fits fixed num_crtc=6 (:452-465).
The read-before-check at dce_v10_0_pageflip_irq :3114 vs :3116 is
latent-only (registered pageflip src_ids 8..18 β crtc_id 0..5).
- dce_v10_0_cursor_set2 width==0 β CUR_SIZE gets 0xFFFFFFFF (:2330):
register cosmetics only, no memory-safety impact.
What ran on the guest
wm_div0.c replicates the exact fixed-point truncation sequence of
dce_v10_0_dram_bandwidth/data_return_bandwidth/dmif_request_bandwidth/
available_bandwidth/latency_watermark with argv-controlled clocks (so the
compiler cannot fold the divide).
Results (build cc 8.3 [DragonFly], -O2 -Wall):
- Zero-clock input: SIGFPE, exit=136 β 3/3 runs (run.log Γ2,
run.2.log), dmesg: pid NNNN (wm_div0), uid 0: exited on signal 8.
- Healthy-clock control (6000000/3000000 kHz): returns 2297, no trap.
Signal 8 in userspace is the same #DE fault the kernel takes on the same instruction; in-kernel this is an unrecoverable panic (kernel #DE has no fixup here).
Why not reproduced
The audit guest is QEMU/KVM with virtio + bochs VGA: pciconf -lv lists
0 devices with vendor=0x1002 and there is no /dev/dri (env.txt), so
amdgpu.ko never attaches and the DCE10 kernel path cannot execute. Kernel
reach requires a Tonga/Fiji dGPU in the affected state (zero default clocks
from VBIOS with DPM off). Impact ceiling on equipped hardware: local DoS
via panic, triggered by an unprivileged DRM client's mode set.
Fix validation
fix.diff guards the three divides (bail to the num_heads==0-style 0
return), adds the available_bandwidth==0 check to the sibling callers, and
adds zero-guards for default_sclk/default_mclk in
amdgpu_atombios_get_clock_info (upstream-root fix mirroring DF-3828).
A patched-kernel build cannot be exercised on this guest (no AMD ASIC β
amdgpu never binds β no observable behavior difference), so fix validation is
not_testable here; the diff is git-apply-clean against the read-only
sys/ tree.
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 both divides 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, no observable kernel behavior difference), 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_v10_0.c:882
- sys/dev/drm/amd/amdgpu/dce_v10_0.c:883
- sys/dev/drm/amd/amdgpu/dce_v10_0.c:884
- sys/dev/drm/amd/amdgpu/dce_v10_0.c:892
- sys/dev/drm/amd/amdgpu/dce_v10_0.c:1024
- sys/dev/drm/amd/amdgpu/dce_v10_0.c:1038
- sys/dev/drm/amd/amdgpu/dce_v10_0.c:2064
- sys/dev/drm/amd/amdgpu/amdgpu_atombios.c:677
- sys/dev/drm/amd/amdgpu/amdgpu_atombios.c:718
- sys/dev/drm/drm_modes.c:1114
Detail
Exploit chain
unprivileged user with /dev/dri/card0 access -> drm_mode_setcrtc -> dce_v10_0_crtc_do_set_base (dce_v10_0.c:1822) -> dce_v10_0_bandwidth_update (:2064/:1145) -> dce_v10_0_program_watermarks -> dce_v10_0_latency_watermark -> divide by 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 (SIGFPE exit=136 twice + healthy control); run.2.log (3rd trap + dmesg 'exited on signal 8' 3/3 + guest /tmp cleanup); VERDICT.md (full chain incl. killed mode->clock/src_width vectors and the dce_v11-template bounds negatives); env.txt (no AMD ASIC on guest); fix.diff (git-apply-clean)
PoC changes
Authored from scratch, modeled on the DF-3828 pack (no seed provided by orchestrator). Inputs via argv to defeat constant folding; dce_v10_0 line numbers and dram-channel math (cik_get_number_of_dram_channels family) adapted from this file.
Verified recommended fix
Bail out of dce_v10_0_latency_watermark when available_bandwidth or wm->disp_clk is 0 (mirroring the num_heads==0 return), and zero-guard ulDefaultEngineClock/ulDefaultMemoryClock seeding in amdgpu_atombios_get_clock_info
Verdict
Source-certain divide-by-zero in the DCE10 display watermark math, the direct DF-3828 (DCE11) twin: dce_v10_0_latency_watermark() divides by available_bandwidth at dce_v10_0.c:882-883 and by wm->disp_clk at :884, all before the function's only guard (num_heads==0, :892, itself dead via the caller gate at :1024) and with no divisor validation anywhere on the caller chain (mode set -> dce_v10_0_crtc_do_set_base :2064 -> dce_v10_0_bandwidth_update :1145 -> program_watermarks :1013). Zero inputs traced end-to-end: with DPM disabled, wm.yclk/wm.sclk come from adev->pm.current_mclk/current_sclk (:1038-1039, :1077-1078), seeded from VBIOS ulDefaultEngineClock/ulDefaultMemoryClock with no zero-guard (amdgpu_atombios.c:677-680) and assigned even on parse failure (:718-719) - identical to the DF-3828 seeding; one zero term collapses dce_v10_0_available_bandwidth (:825-833) 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, MODE_CLOCK_LOW). Arithmetic primitive proven on the guest: exact replica of the bandwidth/watermark math traps SIGFPE (exit=136, signal 8 = #DE, dmesg-confirmed) 3/3 runs while the healthy-clock control returns normally. Kernel-path reproduction impossible on this guest - 0 AMD PCI devices (vendor=0x1002 count 0), no /dev/dri (env.txt), amdgpu never binds - hence not_reproduced for the kernel reach; impact ceiling on Tonga/Fiji hardware in the affected state: local DoS via panic by an unprivileged DRM client mode set.
No comments yet.