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

Sleepable lockmgr acquisition from the GPU interrupt dispatch path: DM_IRQ_TABLE_LOCK in amdgpu_dm_irq_immediate_work/amdgpu_dm_irq_schedule_work runs lockmgr(LK_EXCLUSIVE) inside the serialized DRM ithread (DragonFly port divergence)

Summary

On this port spin_lock_irqsave = lockmgr under cpu_disable_intr - a sleepable acquisition; the DM irq table lock is taken from .process context (the serializer-backed ithread) and held across every high-context vblank/pflip handler, while suspend holds it across MMIO - a concurrent DCE interrupt makes the ithread attempt to sleep inside lockmgr while holding the interrupt serializer: panic/wedged IRQ thread. Upstream uses a true spinlock here (the port swapped lockinit for spin_lock_init). Local DoS ceiling on AMD-graphics systems under interrupt pressure concurrent with suspend. Acquisition chain code-certain; convert-to-true-spinlock fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4732 Β· 5 files
FileTypeDescriptionSize
README.md β€” 2.4 KB ↓ raw
VERDICT.md β€” 4.5 KB ↓ raw
fix.diff β€” 2.1 KB view raw
verdict.json β€” 3.3 KB view raw
env.txt β€” 483 B view raw

DF-4732 β€” amdgpu_dm_irq.c takes a sleepable lockmgr lock from the GPU interrupt dispatch path (DragonFly port)

  • File: sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm_irq.c
  • Severity: Low (local DoS ceiling β€” panic/deadlock; requires AMD GPU + interrupt-vs-suspend race)
  • Confidence: likely (protocol violation is code-certain; runtime manifestation reasoned, not demonstrated β€” no AMD hardware on the audit guest)

The defect

On this port spinlock_t is struct lock and spin_lock_irqsave() is local_irq_save(flags); lockmgr(lock, LK_EXCLUSIVE); (sys/dev/drm/include/linux/spinlock_types.h:38, sys/dev/drm/include/linux/spinlock.h:75-78) β€” a sleepable lock acquired with hardware interrupts force-disabled on the local CPU.

DM_IRQ_TABLE_LOCK (amdgpu_dm_irq.c:50-54) is taken from the GPU ISR dispatch chain:

request_irq() -> bus_setup_intr(..., INTR_MPSAFE, linux_irq_handler, ..., &serializer)
                                                      (sys/dev/drm/linux_irq.c:84-86 β€” serialized ithread)
  -> amdgpu_irq_handler (amdgpu_irq.c:189)
  -> amdgpu_irq_dispatch -> .process = amdgpu_dm_irq_handler (amdgpu_dm_irq.c:588-601, :476-495)
  -> amdgpu_dm_irq_immediate_work (:452)  -- lockmgr LK_EXCLUSIVE in ithread ctx
  -> amdgpu_dm_irq_schedule_work (:427)   -- same

Process-context holders keep this lock for non-trivial durations: amdgpu_dm_irq_suspend (:345-363) holds it across dc_interrupt_set() MMIO read-modify-writes; amdgpu_dm_irq_immediate_work itself holds it while dispatching every high-context handler (:464) β€” vblank/pageflip work.

On contention the serialized ithread attempts to sleep inside lockmgr while holding the interrupt serializer and with cpu_disable_intr() active from local_irq_save β€” sleeping in an ithread / with a serializer held is illegal on DragonFly (panic/deadlock). Upstream Linux uses a true spinlock here, where spin_lock_irqsave in hardirq context is correct.

Why not reproduced

The audit guest has no AMD GPU (env.txt). Building amdgpu into the guest kernel cannot conjure the hardware; the ISR path never runs. Static proof of the acquisition chain is in VERDICT.md.

Fix

fix.diff converts irq_handler_list_table_lock to a true DragonFly struct spinlock (crit_enter + spin_lock in the table-lock macros), matching upstream's non-sleeping irqsave semantics. Not applied to sys/ (read-only audit tree).

VERDICT.md
↓ download raw

DF-4732 VERDICT

Status: untested β€” no AMD GPU on the audit guest; acquisition chain proven statically

What was proven (static, line-cited)

  1. The lock is sleepable on this port. - sys/dev/drm/include/linux/spinlock_types.h:38 β€” typedef struct lock spinlock_t; - sys/dev/drm/include/linux/spinlock.h:75-78 β€” #define spin_lock_irqsave(lock, flags) ({ local_irq_save(flags); lockmgr(lock, LK_EXCLUSIVE); }) - amdgpu_dm_irq.c:305 β€” the lock is initialized with lockinit(&adev->dm.irq_handler_list_table_lock, "agdmihltl", 0, LK_CANRECURSE) (a DFly sleepable lockmgr lock; upstream v4.17 has spin_lock_init here β€” confirmed by diff against torvalds/linux v4.17 during this audit).

  2. The ISR dispatch path acquires it. - sys/dev/drm/linux_irq.c:84-86 β€” request_irq() installs linux_irq_handler via bus_setup_intr(..., INTR_MPSAFE, ..., &irq_entry->irq_lock) with a lwkt_serialize β€” the handler runs in serialized interrupt-thread context. - amdgpu_irq.c:189-199 β†’ amdgpu_irq_dispatch (:397-436, no NULL .process guard β€” that is DF-4713's finding) β†’ the sources registered by this file (amdgpu_dm_irq.c:588-601: dm_crtc_irq_funcs, dm_pageflip_irq_funcs, dm_hpd_irq_funcs β€” all three carry BOTH .set and .process, so this file does not hit the DF-4713 hole) dispatch to amdgpu_dm_irq_handler (:476-495). - amdgpu_dm_irq_handler β†’ amdgpu_dm_irq_immediate_work (:452, takes DM_IRQ_TABLE_LOCK, runs handlers under it at :464) and amdgpu_dm_irq_schedule_work (:427, takes the lock to test list_empty). Both run in ithread context.

  3. The lock is held long enough in process context to make contention real. - amdgpu_dm_irq_suspend (:345-363): holds the lock across dc_interrupt_set() for each HPD source β€” an MMIO read-modify-write per source (irq_service.c:88-99). - Any concurrent DCE interrupt (vblank/pflip storm during modeset, HPD glitch during suspend) takes the same lock from the ithread.

  4. Why sleeping in that context is fatal: DragonFly lockmgr blocks via tsleep on contention; a serialized ithread must not sleep (it holds the interrupt serializer installed by bus_setup_intr), and the shim has also disabled local hardware IRQs (local_irq_save β†’ cpu_disable_intr). Result ceiling: kernel panic ("sleep in ithread" assertion) or a wedged interrupt path β†’ local DoS. This is a DFly-port-specific divergence (upstream's real spinlock is legal in hardirq).

What was NOT demonstrated

No runtime reproduction: the guest has no AMD display adapter (pciconf: only vgapci0 chip=0x11111234 QEMU virtual VGA; no /dev/dri; no drm kld β€” env.txt). The amdgpu module cannot bind, so neither the ISR path nor the suspend race can be triggered. No kernel was built for the same reason (a single-fix kernel build proves nothing without the device).

Exploit chain (theoretical ceiling)

Local unprivileged user in a desktop session on AMD graphics hardware: generate page-flip/vblank interrupt pressure (compositing) or hotplug events (DP cable / USB-C dock emulation by root) while the system enters suspend/resume (amdgpu_dm_irq_suspend holds the table lock across MMIO) β†’ GPU ithread blocks in lockmgr β†’ panic. Impact: local DoS (panic). No path to privilege escalation identified: the corruption is control-flow-internal (no attacker-controlled data crosses the boundary).

Fix validation

fix_status: not_testable (hardware absent). fix.diff converts the table lock to a true struct spinlock with crit_enter()/spin_lock() in the DM_IRQ_TABLE_LOCK macros β€” matching upstream's non-sleeping semantics and the file's actual usage (no recursion on this lock exists; CANRECURSE was unnecessary β€” audited: no high-context handler re-acquires the table lock).

Bottom line

Code-certain protocol violation introduced by the port (sleepable lockmgr + forced irq-disable inside the GPU ithread), DFly-specific, panic ceiling under an interrupt-vs-suspend race on AMD hardware; untestable on this guest for lack of the device.

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

Cannot kernel-validate without AMD GPU. fix.diff restores upstream non-sleeping semantics; no recursion on this lock exists in the file (audited: no high-context handler re-acquires the table lock), so the loss of LK_CANRECURSE is safe.

fix.diff; VERDICT.md 'Fix validation' section
↓ fix.diffper-fix-DF-4732

Confirmed kernel references

Detail

Exploit chain

theoretical only: unprivileged desktop user on AMD hardware generates flip/vblank/HPD interrupt pressure concurrent with amdgpu_dm_irq_suspend (suspend/resume cycle) -> GPU ithread blocks in lockmgr -> panic (local DoS). No privesc path: no attacker-controlled data crosses the lock boundary.

Evidence (decisive lines)

VERDICT.md acquisition-chain walk with path:line for every hop (linux_irq.c:84 -> amdgpu_irq.c:189/:397 -> amdgpu_dm_irq.c:476/:452/:427); env.txt documents absent hardware

PoC changes

n/a - no runtime PoC possible (hardware-gated); static acquisition-chain proof only

Verified recommended fix

Convert irq_handler_list_table_lock to a true struct spinlock (crit_enter+spin_lock in DM_IRQ_TABLE_LOCK macros; spin_init at amdgpu_dm_irq_init), matching upstream irqsave semantics - see fix.diff

Verdict

DragonFly-port protocol violation: DM_IRQ_TABLE_LOCK (amdgpu_dm_irq.c:50) maps to local_irq_save+lockmgr(LK_EXCLUSIVE) (spinlock.h:75-78) on a lock initialized with lockinit/LK_CANRECURSE (:305), and is acquired from the GPU interrupt dispatch path (amdgpu_dm_irq_handler :476 -> amdgpu_dm_irq_immediate_work :452 / amdgpu_dm_irq_schedule_work :427), which runs in the serializer-backed ithread installed by request_irq->bus_setup_intr (linux_irq.c:84-86). lockmgr may sleep; sleeping in a serialized ithread with the interrupt serializer held and hardware IRQs force-disabled is illegal on DragonFly -> panic/deadlock ceiling when an interrupt contends with a process-context holder (amdgpu_dm_irq_suspend holds the lock across dc_interrupt_set MMIO at :345-:363; immediate_work itself holds it across vblank/pflip handler dispatch at :464). Untestable on this guest: no AMD GPU, no /dev/dri, no drm kld (env.txt). DFly-specific divergence - upstream v4.17 uses a true spinlock (diffed).