dce_virtual's crtc_irq .set callback invokes the DragonFly hrtimer_cancel emulation, which may sleep (taskqueue_drain) and migrate CPUs, while amdgpu_irq_update holds adev->irq.lock β atomic-context contract violation, display-irq stall/lock-protocol hazard
Summary
Every vblank enable/disable runs irq_update holding spin_lock_irqsave(adev->irq.lock) across the .set callback -> hrtimer_cancel, which the DF emulation implements with possible CPU migration and a blocking taskqueue_drain - a sleep while holding the exclusive display-irq lock and the crit section. Linux hrtimer_cancel is atomic-safe; the shim violates the contract (i915 locked contexts call it too). Unpriv DRM user on virtual_display amdgpu hardware: display-path stalls/deadlock exposure. Defer-out-of-callback or make-the-shim-non-blocking fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4643 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.6 KB | β raw | |
| VERDICT.md | β | 3.9 KB | β raw | |
| verdict.json | β | 3.0 KB | view raw | |
| manifest.json | β | 601 B | view raw |
DF-4643 β dce_virtual .set callback runs the sleeping DragonFly hrtimer_cancel
under adev->irq.lock
- File:
sys/dev/drm/amd/amdgpu/dce_virtual.c:736(via:744-755), chain intosys/dev/drm/linux_hrtimer.c:121-150 - Severity: Medium (local DoS / lock-protocol violation; config-gated)
- Status: untested β requires an AMD GPU,
kldload amdgpu, andamdgpu.virtual_display=1. The audit guest has no AMD GPU (only QEMU virtual VGAvgapci0) and amdgpu is not in GENERIC.
The chain (all user-triggerable once the driver is up)
Any DRM ioctl that enables/disables a CRTC vblank (page-flip, DRM_IOCTL_WAIT_VBLANK, modeset/DPMS) on a virtual-display GPU:
amdgpu_enable_vblank_kms / amdgpu_disable_vblank_kms amdgpu_kms.c:1172-1194
-> amdgpu_irq_get/put(adev, &adev->crtc_irq, idx) amdgpu_irq.c:499-524
-> amdgpu_irq_update(adev, src, type) amdgpu_irq.c:451
spin_lock_irqsave(&adev->irq.lock) amdgpu_irq.c:458 <-- crit_enter + lockmgr EXCLUSIVE
-> src->funcs->set(...) amdgpu_irq.c:467
= dce_virtual_set_crtc_irq_state dce_virtual.c:744
-> dce_virtual_set_crtc_vblank_interrupt_state dce_virtual.c:715
-> hrtimer_cancel(&crtc->vblank_timer) dce_virtual.c:736
-> lwkt_setcpu_self(timer->gd) linux_hrtimer.c:140 (CPU migration)
-> systimer_del(&timer->st) linux_hrtimer.c:137/141
-> taskqueue_drain(tq, &timer->task) linux_hrtimer.c:144-145
-> TQ_SLEEP/ssleep subr_taskqueue.c:490-495 (BLOCKS)
spin_unlock_irqrestore(&adev->irq.lock) amdgpu_irq.c:468
On Linux, hrtimer_cancel() is atomic-context safe (it spins, never sleeps), which
is why the amdgpu code can call it from the .set callback that the irq core
invokes under a raw spinlock with IRQs off. The DragonFly emulation
(sys/dev/drm/linux_hrtimer.c) violates that contract: cancel may
(a) taskqueue_drain β ssleep β a blocking sleep while the caller holds
adev->irq.lock (a lockmgr lock here) and sits in the crit section entered by
spin_lock_irqsave (local_irq_save = crit_enter, see
sys/dev/drm/include/linux/irqflags.h:34-37, spinlock.h:75-79); and
(b) lwkt_setcpu_self() β migrate the thread across CPUs while it holds that lock.
Every amdgpu interrupt-state update on all CRTCs serializes behind the sleeping
thread; combined with the already-broken cancel state machine (DF-2174: active
cleared by the task epilogue makes cancel return without stopping the systimer;
DF-2175: hrtimer_start_range_ns re-inits an armed systimer), the disable path can
sleep for an unbounded drain and migrate a lock holder, producing long system-wide
stalls of the display irq path and compounding the DF-2174 un-cancellable timer.
This is distinct from DF-2174/2175 (both about active-flag/queue corruption inside
the emulation): the defect here is the context contract β a callback the irq core
calls under a lock/irqsave is legally allowed (per Linux semantics every drm
driver relies on) to call hrtimer primitives, and the emulation's blocking/migrating
implementation of hrtimer_cancel is illegal in that context.
Fix directions
Either (a) in dce_virtual.c: do not touch hrtimers from the .set callback β
record the desired state and arm/cancel the software vblank timer from a work
item (or only from hw_init/hw_fini/SMP-call context), or (b) in
linux_hrtimer.c: make hrtimer_cancel non-blocking in atomic context (spin for
completion / return -EAGAIN semantics) so the Linux atomic-context contract holds.
DF-4643 β VERDICT
Not verified on the guest (untested). The defect is proven by static tracing of
the full call chain (see README.md); it cannot be executed on the audit guest
because there is no AMD PCI device and amdgpu exists only as a kld module
(sys/dev/drm/amd/amd/Makefile, absent from sys/config/X86_64_GENERIC).
What is proven (path:line)
.setis always invoked under the irq lock:amdgpu_irq_update()takesspin_lock_irqsave(&adev->irq.lock, irqflags)(amdgpu_irq.c:458) and callssrc->funcs->set(...)(amdgpu_irq.c:467) before releasing (amdgpu_irq.c:468).- dce_virtual's
.setisdce_virtual_set_crtc_irq_state(dce_virtual.c:744, registered at :757-760) which forwards todce_virtual_set_crtc_vblank_interrupt_state(:752) whose disable branch callshrtimer_cancel(:736). - The emulation's
hrtimer_cancel(linux_hrtimer.c:121-150) can: -lwkt_setcpu_self(timer->gd)(line 140) β CPU migration of the caller, -systimer_del(137/141) β withKKASSERT(gd == mycpu && !SYSTF_IPIRUNNING)(kern_systimer.c:220) reachable only by construction of the emulation, -taskqueue_drain(144-145) βTQ_SLEEP/ssleep(subr_taskqueue.c:490-495) β a blocking sleep. spin_lock_irqsavein the DF drm port =local_irq_save(crit_enter, irqflags.h:58-62/34-37) +lockmgr(LK_EXCLUSIVE)(spinlock.h:75-79) β so the caller holds the exclusive display-irq lock and a crit section across the sleeping/migrating cancel.- User reachability once the driver runs:
amdgpu_enable_vblank_kms/amdgpu_disable_vblank_kms(amdgpu_kms.c:1172-1194) are the drm_driver vblank hooks; every drmWaitVBlank/page-flip/DPMS cycle drives them.dce_virtual_crtc_dpms(dce_virtual.c:142) callsamdgpu_irq_updatedirectly as well.
Why Medium / dos and not higher
- Gate: needs an AMD GPU, the amdgpu module loaded by an admin, and the
non-default
amdgpu.virtual_display=1β "requires unusual config" (Medium). - On DragonFly the lock is a lockmgr lock (sleepable), so this is not the instant hard-freeze it would be on Linux's raw spinlock; the practical impact is a display-irq-path stall (all vblank enable/disable serialized behind a sleeping lock holder) that compounds DF-2174's un-cancellable timer, plus thread-migration of a lock holder β a lock-protocol violation with real deadlock exposure under contention, not a demonstrated RCE primitive.
Recommended fix (local, dce_virtual.c)
Defer hrtimer arm/cancel out of the .set callback:
--- a/sys/dev/drm/amd/amdgpu/dce_virtual.c
+++ b/sys/dev/drm/amd/amdgpu/dce_virtual.c
@@ -715,6 +715,13 @@ static void dce_virtual_set_crtc_vblank_interrupt_state(struct amdgpu_device *ad
enum amdgpu_interrupt_state state)
{
+ /*
+ * .set runs under adev->irq.lock (spin_lock_irqsave in
+ * amdgpu_irq_update); the DragonFly hrtimer emulation may sleep or
+ * migrate CPUs in hrtimer_cancel(), so arm/cancel must be deferred
+ * to process context.
+ */
if (crtc >= adev->mode_info.num_crtc || !adev->mode_info.crtcs[crtc]) {
DRM_DEBUG("invalid crtc %d\n", crtc);
return;
(concretely: store state in the crtc and let a scheduled work item perform
hrtimer_init/start/cancel; alternatively make linux_hrtimer.c's cancel
atomic-context-safe system-wide.)
fix.diff was not authored as a standalone applied-and-built artifact because no
runtime verification was possible on this guest (fix_status: not_testable).
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.
(No fix.diff for this item: documented recommendation only - see Fix above.)
Fix verification
not_testableNo runtime verification was possible (no AMD hardware); sketch of the local fix is in VERDICT.md.
VERDICT.md fix section.
Confirmed kernel references
- sys/dev/drm/amd/amdgpu/dce_virtual.c:736
- sys/dev/drm/amd/amdgpu/dce_virtual.c:744
- sys/dev/drm/amd/amdgpu/amdgpu_irq.c:458
- sys/dev/drm/amd/amdgpu/amdgpu_irq.c:467
- sys/dev/drm/amd/amdgpu/amdgpu_kms.c:1172
- sys/dev/drm/linux_hrtimer.c:121
- sys/dev/drm/linux_hrtimer.c:140
- sys/dev/drm/linux_hrtimer.c:144
- sys/kern/subr_taskqueue.c:490
- sys/dev/drm/include/linux/spinlock.h:75
- sys/dev/drm/include/linux/irqflags.h:34
Detail
Evidence (decisive lines)
README.md/VERDICT.md carry the complete traced chain with path:line for every hop; guest facts (no AMD PCI device, amdgpu absent from GENERIC) recorded in notes.
PoC changes
none β no PoC seed; driver not loadable on guest
Verified recommended fix
Defer hrtimer arm/cancel out of the .set callback in dce_virtual.c (record state, act from a work item), or make the DF hrtimer emulation's hrtimer_cancel non-blocking in atomic context.
Verdict
Proven statically, not executable on the audit guest (no AMD GPU; amdgpu is kldload-only). dce_virtual's crtc_irq .set callback (dce_virtual.c:744 -> :715 -> hrtimer_cancel at :736) is always invoked from amdgpu_irq_update under spin_lock_irqsave(&adev->irq.lock) (amdgpu_irq.c:458-468). The DF hrtimer emulation's hrtimer_cancel (linux_hrtimer.c:121-150) may taskqueue_drain -> ssleep (subr_taskqueue.c:490-495) and lwkt_setcpu_self (CPU migration) β illegal in the atomic context Linux hrtimer semantics guarantee, so every vblank enable/disable (amdgpu_kms.c:1172-1194, user-triggerable via DRM ioctls) can sleep or migrate while holding the exclusive display-irq lock: display-irq-path stall/deadlock exposure, compounding the known un-cancellable-timer defects DF-2174/DF-2175 (distinct root cause: blocking/migrating cancel vs active-flag corruption). Expected impact dos; gated by AMD hardware + amdgpu.virtual_display=1 -> Medium.
No comments yet.