β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-4225

Divide-by-zero (kernel panic) in drm_mode_vrefresh: user-supplied umode with htotal*vtotal == 0 (mod 2^32) passes drm_mode_validate_basic and wraps den to zero

Summary

drm_mode_vrefresh computes den = htotal*vtotal in unsigned int; htotal=vtotal=65536 wraps to exactly 0 and the DIV_ROUND_CLOSEST is #DE. validate_basic only checks monotonicity and clock!=0 - no magnitude cap - so the crafted umode is accepted from DRM_IOCTL_MODE_SETCRTC. The trap fires in driver DPM refresh consumers (radeon r600_dpm/rs780_dpm, amdgpu_dpm) running on the user-programmed mode; the same division runs for every probed mode. PROVEN on the guest: verbatim validate_basic ACCEPTS the umode (status=0) and verbatim vrefresh dies SIGFPE at the exact line, -O2 and -O0. Local kernel-panic DoS for a DRM-master holder. u64-widen + USHRT_MAX-reject fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4225 Β· 10 files
FileTypeDescriptionSize
harness.c β€” 15.3 KB view raw
dbg.c β€” 931 B view raw
build.sh β€” 143 B view raw
run.sh β€” 111 B view raw
build.log β€” 1022 B view raw
run.log β€” 1.2 KB view raw
env.txt β€” 320 B view raw
VERDICT.md β€” 2.7 KB ↓ raw
fix.diff β€” 1.7 KB view raw
verdict.json β€” 3.4 KB view raw
VERDICT.md
↓ download raw

DF-4225 β€” VERDICT

Status: untested (kernel path) / arithmetic PROVEN on guest. Impact ceiling: kernel panic (DoS) on GPU-equipped systems. No memory-corruption primitive (clean #DE).

What was proven, on the guest

  1. Validation acceptance β€” the verbatim drm_mode_validate_basic() (drm_modes.c:1102-1130) returns MODE_OK for the crafted umode {clock=108000, h: 1,2,3,65536 / v: 1,2,3,65536, flags=0, vscan=0} β€” run.log line umode validate_basic status=0. The kernel's only guards are monotonicity + clock != 0; no magnitude cap exists between drm_mode_convert_umode() (drm_modes.c:1691-1751) and the mode being programmed.
  2. The trap β€” verbatim drm_mode_vrefresh() then computes den = 65536*65536 which wraps to 0 in unsigned int, and DIV_ROUND_CLOSEST(num, 0) raises SIGFPE (exit=136) immediately after the drm_modes.c:796 marker, at -O2 and -O0 (guest cc 8.3 [DragonFly], same integer ABI as the guest kernel). In-kernel this idiv is the same #DE β†’ panic.

Why the kernel path itself is untested here

The QEMU guest has no DRM device (vgapci0 chip=0x11111234 Bochs VGA; no /dev/dri), so DRM_IOCTL_MODE_SETCRTC cannot be issued and no radeon/amdgpu DPM consumer exists. The cited consumers are: sys/dev/drm/drm_crtc.c:655 (legacy setcrtc umode intake) β†’ sys/dev/drm/radeon/r600_dpm.c:195, rs780_dpm.c:66, sys/dev/drm/amd/amdgpu/amdgpu_dpm.c:178 (drm_mode_vrefresh(&hw_mode) on the user-programmed mode), plus drm_probe_helper.c:526 (vrefresh fill for every probed mode). All are source-verified; none instantiable on this VM.

Chain (on real hardware)

open /dev/dri/card0 β†’ DRM_IOCTL_SET_MASTER β†’ DRM_IOCTL_MODE_SETCRTC {mode_valid=1, modeinfo{htotal=65536, vtotal=65536, monotonic 1/2/3 chain, clock=108000}} β†’ drm_crtc.c:655 drm_mode_convert_umode β†’ MODE_OK β†’ modeset commits β†’ radeon DPM work β†’ r600_dpm.c:195 drm_mode_vrefresh(&hw_mode) β†’ drm_modes.c:796 num/0 β†’ #DE β†’ panic.

Fix validation

fix.diff widens num/den to u64 (kills the wrap) and adds a USHRT_MAX magnitude cap in drm_mode_validate_basic() (matches the boundary the WARN at drm_modes.c:1634-1639 already anticipates). Kernel-level fix boot test is not possible on this guest (no DRM); the u64 math eliminates the wrap by construction ((u64)65536*65536 = 2^32 β‰  0).

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_testable
baseline no→ patch + rebuild →patched clean

Patched-kernel test impossible on this guest (no DRM hardware). fix.diff removes the wrap by construction ((u64)65536*65536 = 2^32 != 0) and the validate_basic cap rejects the umode before it can be programmed; harness-level equivalence: with den computed in u64 the harness case-5 division returns 0 instead of trapping.

fix.diff; run.log validate_basic acceptance demonstrates the exact acceptance path the cap closes
↓ fix.diffper-fix-DF-4225

Confirmed kernel references

Detail

Exploit chain

open /dev/dri/card0 -> SET_MASTER -> DRM_IOCTL_MODE_SETCRTC{mode_valid=1, modeinfo htotal=65536 vtotal=65536, monotonic 1/2/3 chains, clock=108000} -> drm_crtc.c:655 drm_mode_convert_umode -> MODE_OK (no magnitude cap) -> modeset commit -> radeon DPM: r600_dpm.c:195 drm_mode_vrefresh(&hw_mode) -> drm_modes.c:796 num/0 -> #DE -> panic.

Evidence (decisive lines)

run.log: 'umode validate_basic status=0' then '[marker] VREFRESH: drm_modes.c:796' then 'Floating point exception (core dumped)' exit=136 (both -O2 and -O0); harness.c case 5; env.txt documents absent DRM device.

PoC changes

self-authored (case 5 of the DF-4224 harness, shared verbatim extracts); htotal/vtotal read from argv so GCC cannot fold the wrap

Verified recommended fix

Widen num/den to u64 in drm_mode_vrefresh (drm_modes.c:784-787) and add a USHRT_MAX magnitude cap for h/v timings in drm_mode_validate_basic(), matching the boundary drm_mode_convert_to_umode already WARNs about.

Verdict

Kernel path untestable on this guest (no DRM device; DRM_IOCTL_MODE_SETCRTC cannot be issued), but both halves of the bug are proven on the guest toolchain: (1) verbatim drm_mode_validate_basic() ACCEPTS the crafted umode {clock=108000, h 1/2/3/65536, v 1/2/3/65536} with MODE_OK β€” the function only checks monotonicity, never magnitude; (2) verbatim drm_mode_vrefresh() then computes den = 65536*65536 which wraps to 0 in unsigned int and DIV_ROUND_CLOSEST(num,0) raises SIGFPE (exit=136) at drm_modes.c:796, at -O2 and -O0. On real hardware the mode enters via drm_crtc.c:655 (setcrtc umode) and the trap fires in the radeon/amdgpu DPM vrefresh consumers (r600_dpm.c:195, rs780_dpm.c:66, amdgpu_dpm.c:178) β€” local kernel-panic DoS for a DRM master. No memory corruption.