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)
PoC verification
Evidence pack
findings/poc/DF-4182 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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)
DRM_IOCTL_MODE_PAGE_FLIPβ core takes a vblank reference (drm_plane.c:1053drm_crtc_vblank_get) βradeon_crtc_page_flip_target()(radeon_display.c:474): pins the new BO (:524), setsflip_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 (:592queue_work).- User immediately disables the CRTC (SETCRTC with no mode / DPMS off).
radeon_flip_work_func()(radeon_display.c:402): waits for fences, then the wait loopwhile (radeon_crtc->enabled && ...)(:449-458) β theenabledcheck only decides whether to wait for vblank; execution continues regardless.- Under the event lock:
radeon_irq_kms_pflip_irq_get()(:464),radeon_page_flip()programs the surface base on the disabled CRTC (:467), andflip_status = RADEON_FLIP_SUBMITTED(:469). - 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 resetsflip_status(grep across sys/dev/drm/radeon/: only radeon_display.c:381 inradeon_crtc_handle_flip). - Consequences:
radeon_crtc_page_flip_targetrejects every future flip with -EBUSY (:578-583); the vblank event never fires (drm_crtc_send_vblank_eventonly in the completion path, :386); thedrm_crtc_vblank_getreference 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
inconclusiveCompile-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).
Confirmed kernel references
- sys/dev/drm/radeon/radeon_display.c:449
- sys/dev/drm/radeon/radeon_display.c:467
- sys/dev/drm/radeon/radeon_display.c:469
- sys/dev/drm/radeon/radeon_display.c:381
- sys/dev/drm/radeon/radeon_display.c:390
- sys/dev/drm/radeon/radeon_display.c:578
- sys/dev/drm/radeon/radeon_display.c:592
- sys/dev/drm/drm_plane.c:1053
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).
No comments yet.