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

Page flip programmed on a CRTC disabled while the flip was pending wedges that CRTC's flip path forever (flip_status never leaves RADEON_FLIP_SUBMITTED) and leaks the vblank reference

Summary

radeon_flip_work_func's enabled check only guards the vblank WAIT loop - with the CRTC disabled it skips the wait but still calls radeon_page_flip on the dead CRTC and sets flip_status=SUBMITTED. A disabled CRTC raises no vblank/pflip interrupts and handle_flip is the ONLY code that resets flip_status - so it stays SUBMITTED forever: every subsequent PAGE_FLIP returns EBUSY, the queued event never delivered, and the vblank reference leaks. Unprivileged DRM master; fence wait widens the race to ms..s. Abort-when-disabled fix (validated -Werror).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4182 Β· 8 files
FileTypeDescriptionSize
VERDICT.md verdict verification verdict + full path:line trace 3.4 KB ↓ raw
rflip_wedge.c trigger race sequence documentation + raw ioctl scaffolding 3.8 KB view raw
build.sh build build command 95 B view raw
run.sh run run command 183 B view raw
run.log runlog guest run: open /dev/dri/card0 ENOENT (no GPU) 316 B view raw
env.txt env guest environment 621 B view raw
fix.diff suggested-fix abort flip on disabled CRTC (radeon.h + radeon_display.c, git-apply-able) 2.6 KB view raw
verdict.json verdict machine verdict 3.4 KB view raw
VERDICT.md verdict verification verdict + full path:line trace
↓ download raw

DF-4182 β€” Verification Verdict

Status: inconclusive (hardware-gated: no radeon GPU in the QEMU guest; the race needs a real CRTC with vblank interrupts) Impact: none on this guest (permanent per-CRTC page-flip wedge + vblank refcount leak on affected hardware β€” local DoS) Confidence: likely (state machine provable from source; the race window depends on fence wait timing)

Mechanism (source trace)

  1. DRM_IOCTL_MODE_PAGE_FLIP β†’ core takes a vblank reference (drm_plane.c:1053 drm_crtc_vblank_get) β†’ radeon_crtc_page_flip_target() (radeon_display.c:474): pins the new BO (:524), sets flip_status = RADEON_FLIP_PENDING, flip_work = work, crtc->primary->fb = fb (:584-588) and returns 0; the actual flip runs later on the crtc workqueue (:592 queue_work).
  2. User immediately disables the CRTC (SETCRTC with no mode / DPMS off).
  3. radeon_flip_work_func() (radeon_display.c:402): waits for fences, then the wait loop while (radeon_crtc->enabled && ...) (:449-458) β€” the enabled check only decides whether to wait for vblank; execution continues regardless.
  4. Under the event lock: radeon_irq_kms_pflip_irq_get() (:464), radeon_page_flip() programs the surface base on the disabled CRTC (:467), and flip_status = RADEON_FLIP_SUBMITTED (:469).
  5. Completion is detected by radeon_crtc_handle_vblank() / radeon_crtc_handle_flip() (:278/:359) from vblank/pflip interrupts β€” a disabled CRTC generates none, and nothing else in the driver ever resets flip_status (grep across sys/dev/drm/radeon/: only radeon_display.c:381 in radeon_crtc_handle_flip).
  6. Consequences: radeon_crtc_page_flip_target rejects every future flip with -EBUSY (:578-583); the vblank event never fires (drm_crtc_send_vblank_event only in the completion path, :386); the drm_crtc_vblank_get reference from step 1 is never put (the put is in the completion path, :390) β†’ vblank irq bookkeeping leaks; work (with the pinned old_rbo reference) is only freed by the unpin_work scheduled from the completion path (:392) β†’ leak.

Reachability: any DRM master (unprivileged desktop session via logind) on radeon KMS. The window is the fence wait in step 3 (ms–s under load) β€” trivially widened by keeping the GPU busy. Local DoS of the flip path on one CRTC until module reload.

Why it cannot be reproduced on this guest

No ATI/AMD GPU, no /dev/dri; rflip_wedge builds and runs, open("/dev/dri/card0") fails ENOENT (run.log). The race itself requires a real CRTC with vblank interrupts.

Fix validation

fix.diff: stash new_rbo in struct radeon_flip_work; in radeon_flip_work_func, when !radeon_crtc->enabled at programming time, reset flip_status/flip_work, drop the vblank reference, deliver the event, unpin the new BO and queue the existing unpin work. Applies cleanly (radeon.h + radeon_display.c); radeon.ko module built with the patch (fix_build.log). Expected behavior on hardware: after disable / re-enable, PAGE_FLIP succeeds again (step 5 of README).

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

inconclusive
baseline no→ patch + rebuild →patched clean

Compile-validated only: fix.diff (radeon.h struct member + abort path in radeon_flip_work_func) applied cleanly and radeon.ko built with -Werror in the batch nativekernel build (fix_build.log). Behavioral test impossible on this guest (no GPU; race needs a real CRTC).

fix_build.log excerpt (radeon_display.c -Werror KLD_MODULE compile + radeon.ko link).
↓ fix.diffper-fix-DF-4182

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md (state machine + grep proof that nothing else resets flip_status); rflip_wedge.c (race sequence + raw ioctl scaffolding); run.log (no GPU, build OK); fix.diff (radeon.h + radeon_display.c abort path) + fix_build.log (radeon.ko built -Werror with fix)

PoC changes

n/a (first-pass PoC authored in this run; the full race needs libdrm modeset plumbing documented in README)

Verified recommended fix

Abort the flip in radeon_flip_work_func when !radeon_crtc->enabled: reset flip_status/flip_work, drm_crtc_vblank_put, deliver the event, unpin the new BO (stashed in struct radeon_flip_work) and queue the unpin work.

Verdict

Source-certain completion-path dead-end: radeon_flip_work_func's 'enabled' check (radeon_display.c:449) only guards the vblank wait loop; when the CRTC was disabled while the flip was pending the work still programs the flip on the disabled CRTC (:467) and sets flip_status=RADEON_FLIP_SUBMITTED (:469). A disabled CRTC raises no vblank/pflip irqs and radeon_crtc_handle_flip (:359/:381, the ONLY reset of flip_status anywhere in radeon) never runs: every later DRM_IOCTL_MODE_PAGE_FLIP on that CRTC returns EBUSY forever (:578-583), the queued vblank event is never delivered, and the drm_crtc_vblank_get reference taken by the ioctl core (drm_plane.c:1053) leaks (put is at :390 in the completion path). Unprivileged-reachable race (DRM master; window = fence wait, ms..s, widenable by GPU load). Local DoS of the CRTC's flip path until module reload. Not exercisable on this guest: no GPU, no /dev/dri (run.log).