drm_mode_page_flip_ioctl: -EDEADLK retry bypasses the only drm_crtc_vblank_put - one vblank reference leaked per page flip that succeeds after a deadlock retry
Summary
vblank_get is taken before the acquire ctx (outside the retry loop); the sole put executes only on a non-EDEADLK exit - after the goto retry block. Any flip that hits -EDEADLK, backs off, then succeeds returns ret==0 and leaks the vblank ref: monotonic refcount growth, vblank IRQ permanently enabled, eventual counter saturation. DRM_MASTER-gated; live only on radeon/legacy-DCE amdgpu (page_flip_target); latent on stock DFly today (no in-tree producer of the inversion against this ioctl's lock order); byte-identical upstream and still in master. Bracket-the-reference fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4437 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.4 KB | β raw | |
| VERDICT.md | β | 3.6 KB | β raw | |
| verdict.json | β | 3.0 KB | view raw | |
| manifest.json | β | 1.0 KB | view raw | |
| fix.diff | β | 1.3 KB | view raw | |
| env.txt | β | 733 B | view raw |
DF-4437 β drm_mode_page_flip_ioctl: -EDEADLK retry bypasses drm_crtc_vblank_put (vblank refcount leak)
Severity / class
Low (local, DRM_MASTER-gated, latent trigger; no memory corruption).
Upstream-inherited: identical logic in Linux v5.0 and, as of 2026-09, in
upstream master (drm_mode_page_flip_ioctl tail unchanged).
Root cause
sys/dev/drm/drm_plane.c, drm_mode_page_flip_ioctl:
:1053r = drm_crtc_vblank_get(crtc);β taken ONCE, beforedrm_modeset_acquire_init(), but only whencrtc->funcs->page_flip_targetexists.:1089retry:label INSIDE the modeset loop.:1186-1190onret == -EDEADLK:drm_modeset_backoff(), thengoto retryβ jumping straight back into the loop.:1195-1196if (ret && crtc->funcs->page_flip_target) drm_crtc_vblank_put(crtc);β the ONLY put, placed AFTER the EDEADLK block and gated onret != 0.
Control flow: EDEADLK (ret != 0) β out: β goto retry β second
iteration succeeds β ret == 0 β tail put never executes β one vblank
reference leaked per page flip that succeeded after a deadlock retry.
Reachability on DragonFly (honest assessment)
- The ioctl is
DRM_MASTER-gated (sys/dev/drm/drm_ioctl.c:673), so the attacker is the display master β on a console with no compositor running, any local user can become master via DRM_IOCTL_SET_MASTER; otherwise the compositor itself (multi-threaded) races itself. - The path is live only for CRTC drivers implementing
page_flip_target: in-tree that is radeon (sys/dev/drm/radeon/radeon_display.c:666) and amdgpu legacy DCE (sys/dev/drm/amd/amdgpu/amdgpu_display.c:146, wired in dce_v8/v10/ v11/virtual). amdgpu DC and simple-kms drivers are unaffected. - Producing
-EDEADLKinside this ioctl requires a ww-modeset-lock inversion (this function takes crtc->mutex then plane->mutex; every in-tree core path β setplane/cursor/atomic/setcrtc β uses the same crtc-before-plane order, and the legacy drivers below don't invert). The inversion producer exists in mainline (e.g.drm_atomic_helper_update_planelocks plane state before crtc state) but is only compiled for atomic drivers, which on DF don't carrypage_flip_target. So on stock DragonFly today the leak is LATENT: certain code defect, no identified in-tree trigger producer. Confidence "likely" reflects exactly this.
Impact ceiling
Each occurrence increments the CRTC's vblank refcount without a matching
put: vblank IRQ stays permanently enabled (perf/idle-power), and
drm_crtc_vblank_get refcount grows monotonically; after ~2^31 leaks
the counter overflows (warn spam / saturation). No kernel memory
corruption; worst case is a local display-subsystem resource-exhaustion
DoS on the affected ASICs, requiring sustained winning deadlock-retries.
Why not guest-tested
The QEMU audit guest has no DRM device at all (/dev/dri absent; only an
unbound QEMU std-VGA chip β see env.txt), and the leak needs a
page_flip_target-capable GPU (radeon / pre-DC amdgpu) plus a lock-inversion
producer that stock DF does not currently ship. Low severity, non-trivial
local trigger β QEMU verification not warranted per audit contract.
Fix
fix.diff β bracket the ref with a vblank_ref_held flag: (re)take the
reference at the top of the retry loop when not held, and drop it at
out: before the EDEADLK decision, clearing the flag; every exit path
then pairs get/put exactly once.
DF-4437 VERDICT β certain code defect, latent trigger on stock DF
Reproduced?
No runtime verification performed (status: untested). Two independent
blockers on the audit guest: (1) no DRM device exists in the guest
(/dev/dri absent, only an unbound QEMU std-VGA chip β env.txt); (2) the
leak needs a page_flip_target-capable driver (radeon / pre-DC amdgpu)
raced by a ww-modeset-lock-inversion producer, which stock DragonFly
does not currently ship for those drivers. Low severity, non-trivial
local trigger β QEMU verification not mandated by the audit contract.
Why the logic defect is certain (control-flow proof)
sys/dev/drm/drm_plane.c, drm_mode_page_flip_ioctl:
:1049-1055ifcrtc->funcs->page_flip_target:r = drm_crtc_vblank_get(crtc);β reference taken exactly once, BEFOREdrm_modeset_acquire_init()at:1088, i.e. outside the retry loop.:1088-1089drm_modeset_acquire_init(...); retry::1179out:β¦:1186-1190if (ret == -EDEADLK) { ret = drm_modeset_backoff(&ctx); if (!ret) goto retry; }:1195-1196if (ret && crtc->funcs->page_flip_target) drm_crtc_vblank_put(crtc);
Trace: iteration 1 returns -EDEADLK (from drm_modeset_lock at
:1090/:1093 or the driver callback at :1160/:1165) β out: β
EDEADLK branch β goto retry (skipping the put at :1195) β iteration
2 succeeds β ret == 0 β the ret && guard makes the put a no-op β
the vblank reference acquired at :1053 is never released. One leaked
reference per successfully retried flip; repeatable per contention
event.
This tail is byte-identical in Linux v5.0 (the DF copy's origin) and in upstream master as of 2026-09 β the defect is upstream-inherited and still latent upstream.
Reachability analysis (what keeps this at Low)
- Gate: DRM_IOCTL_MODE_PAGE_FLIP is DRM_MASTER-gated
(
sys/dev/drm/drm_ioctl.c:673). Unprivileged reach requires the attacker to BE the master (possible on a headless/console box where they win DRM_IOCTL_SET_MASTER) or to be the compositor. - Live drivers: radeon (
sys/dev/drm/radeon/radeon_display.c:666) and amdgpu legacy DCE v8/v10/v11/virtual (sys/dev/drm/amd/amdgpu/amdgpu_display.c:146) implementpage_flip_target; DC-based amdgpu does not. - EDEADLK producer: this ioctl takes crtc->mutex then plane->mutex.
Every in-tree core path (setplane lock_all, cursor ioctl, atomic
check, setcrtc) uses the same crtc-before-plane order, so no ww cycle
is generated with them;
drm_atomic_helper_update_plane(which locks plane state before crtc state, creating a real inversion) exists only in atomic drivers that don't carrypage_flip_target. Conclusion: on stock DF the trigger currently has no producer; the defect becomes exploitable the day such a producer is compiled in. Confidence "likely" (defect certain, trigger latent).
Impact ceiling
Per leaked reference: vblank->refcount grows by one and the vblank IRQ
can never be disabled for that CRTC (idle-power/perf regression, warn
spam on counter saturation after ~2^31 events). No kernel memory
corruption, no info leak. Local display-subsystem DoS at worst.
Fix validation
Not applicable (no runtime repro). fix.diff brackets the reference
with a vblank_ref_held flag so every exit path and every retry pairs
get/put exactly once.
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_testableNo behavior validation possible (no GPU, no inversion producer in guest); fix.diff get/put-brackets every exit and retry path; applies cleanly (patch -p1 --dry-run OK, offset 2 lines)
findings/poc/DF-4437/fix.diff (patch -p1 --dry-run: OK)
Confirmed kernel references
Detail
Evidence (decisive lines)
findings/poc/DF-4437/VERDICT.md (full control-flow trace + reachability analysis + upstream parity); env.txt (guest DRM absence); fix.diff (applies cleanly with 2-line offset, patch --dry-run verified)
PoC changes
No PoC authored: needs a page_flip_target-capable GPU (radeon/pre-DC amdgpu) plus a lock-inversion producer not shipped on DF; guest has no DRM device
Verified recommended fix
Bracket the vblank reference with a vblank_ref_held flag: (re)acquire at retry-loop top when not held, release at out: before the -EDEADLK backoff decision
Verdict
Certain control-flow defect, latent trigger on stock DragonFly. drm_mode_page_flip_ioctl takes drm_crtc_vblank_get (drm_plane.c:1053) once, before the modeset retry loop, but the only drm_crtc_vblank_put (:1195-1196) sits AFTER the -EDEADLK 'goto retry' (:1186-1190) and is gated on 'ret' - so a flip that succeeds after a deadlock retry returns ret==0 and never drops the reference: one vblank refcount leak per retried flip, keeping the vblank IRQ permanently enabled and growing the CRTC refcount monotonically. Path is live only for page_flip_target drivers (radeon radeon_display.c:666, amdgpu legacy DCE amdgpu_display.c:146) and is DRM_MASTER-gated (drm_ioctl.c:673). On stock DF no in-tree path produces the required ww-lock inversion against this ioctl's crtc-then-plane order (the plane-then-crtc locker drm_atomic_helper_update_plane exists only in atomic drivers, which don't carry page_flip_target), so the defect is latent today and becomes a local display DoS the day such a producer ships. Byte-identical logic in Linux v5.0 and current upstream master. Untested: guest has no DRM device (/dev/dri absent) and lacks radeon/amdgpu hardware. Low severity.
No comments yet.