flr_work never drained at mailbox teardown β xgpu_ai_mailbox_put_irq() lacks cancel_work_sync(), so a queued FLR work survives put_irq and uses amdgpu_device after hw_fini/free (UAF at driver detach)
Summary
The teardown hook only refcounts the irq down - nothing ever cancels flr_work (only 4 refs in-tree), so a queued FLR work executes through container_of back to adev, mutex/mmdio peeks, and amdgpu_device_gpu_recover AFTER hw_fini (and past drm_dev_put at detach = UAF; on suspend/recovery it forces an unintended second gpu_recover). cancel_work_sync exists in the shim and is already the idiom at amdgpu_irq.c:279 - a plain omission. One-line drain fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4976 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.5 KB | β raw | |
| VERDICT.md | β | 4.4 KB | β raw | |
| fix.diff | β | 695 B | view raw | |
| verdict.json | β | 3.4 KB | view raw | |
| manifest.json | β | 1.1 KB | view raw |
DF-4976 β flr_work never canceled/flushed at mailbox teardown (put_irq): queued work outlives the device
File: sys/dev/drm/amd/amdgpu/mxgpu_ai.c (pass 2, GLM 5.3)
What
xgpu_ai_mailbox_put_irq() (mxgpu_ai.c:365-369) β the designated teardown
hook, invoked from soc15_common_hw_fini (soc15.c:843-846) β only drops the
two irq sources. It never cancel_work_sync()/flush_work()s
adev->virt.flr_work, and no other teardown path in the tree does either
(the only references to flr_work are mxgpu_ai.c:235/237/296/360 and
amdgpu_virt.h:246).
A flr_work that was queued by xgpu_ai_mailbox_rcv_irq (mxgpu_ai.c:296)
shortly before teardown survives put_irq:
- the handler
xgpu_ai_mailbox_flr_work(mxgpu_ai.c:235-271) immediately touches the device:mutex_trylock(&adev->lock_reset)(:250),RREG32_NO_KIQmailbox peeks in amsleep(10)loop for up to 500 ms (AI_MAILBOX_POLL_FLR_TIMEDOUT, mxgpu_ai.h:239/β¦), thenamdgpu_device_gpu_recover(adev, NULL)(:270).
Consequences, in increasing severity:
- During GPU recovery / suspend (put_irq runs per IP hw_fini): the
surviving work re-enters
amdgpu_device_gpu_recoverconcurrently with the recovery/suspend cycle that disabled its irq β the mutex serializes them into an unintended back-to-back double recovery racing the fini/resume state machine. - During driver detach (
amdgpu_driver_unload_kms, amdgpu_kms.c:75 βamdgpu_device_finiβ per-IP hw_fini β put_irq, thendrm_dev_put(dev)amdgpu_drv.c:974 freesdev->dev_private==adev): the queued work dereferences freed memory βcontainer_ofon the deadflr_work(mxgpu_ai.c:237-238), lock/unlock of a freedstruct lock, MMIO reads through freed register mappings, and a call intoamdgpu_device_gpu_recoveron the freed device. Use-after-free.
The DragonFly shim provides the missing idiom β cancel_work_sync exists
(sys/dev/drm/include/linux/workqueue.h:131) and is already used in this
very subsystem's teardown (amdgpu_irq.c:279/310
cancel_work_sync(&adev->reset_work)), so the omission is plainly a bug,
not a shim limitation.
Aggravating note (functional, same root): get_irq runs only in
late_init (probe/resume). GPU recovery runs hw_finiβput_irq but never
re-runs late_init, so after the first recovery the mailbox RCV irq stays
disabled β FLR notifications are then silently lost, which both widens the
stale-work hazard window at the next resume and degrades host FLR
coordination.
Threat model
SR-IOV VF guest (Vega20-class). Queuing requires an IDH_FLR_NOTIFICATION
from the host/PF (mxgpu_ai.c:294-296) β host-timed, like DF-4755/4975 β
landing just before hw_fini/put_irq of a detach, suspend, or recovery.
A guest-local user can widen the recovery-path variant by causing GPU job
timeouts (any user with /dev/dri access can hang the ring long enough for
TDR), which forces the recovery teardown that put_irq's without checking
for a pending FLR work. Impact ceiling: use-after-free at detach; race at
recovery. Low (host-timed, teardown window, VF-only), consistent with
the family's ratings.
Recommended fix (fix.diff in this pack)
void xgpu_ai_mailbox_put_irq(struct amdgpu_device *adev)
{
amdgpu_irq_put(adev, &adev->virt.ack_irq, 0);
amdgpu_irq_put(adev, &adev->virt.rcv_irq, 0);
+ cancel_work_sync(&adev->virt.flr_work);
}
Reproduce / verification status
Not runnable on the audit guest (no AMD GPU / SR-IOV; see env.txt in the DF-4975 pack, identical guest). Verification is by code trace (VERDICT.md).
DF-4976 VERDICT β VERIFIED-BY-TRACE, NOT EXECUTED (hardware absent)
status: untested | reproduced: 0 | impact: none (on this guest) | confidence: likely
Defect proof (path:line)
-
The work and its only queueing site.
flr_worklives instruct amdgpu_virt(amdgpu_virt.h:246). It is queued at exactly one site:xgpu_ai_mailbox_rcv_irqmxgpu_ai.c:296 (schedule_work(&adev->virt.flr_work)), gated on anIDH_FLR_NOTIFICATIONfrom the PF (mxgpu_ai.c:294) β i.e., the host decides when this work appears. -
The teardown hook does not drain it.
xgpu_ai_mailbox_put_irq(mxgpu_ai.c:365-369):c void xgpu_ai_mailbox_put_irq(struct amdgpu_device *adev) { amdgpu_irq_put(adev, &adev->virt.ack_irq, 0); amdgpu_irq_put(adev, &adev->virt.rcv_irq, 0); }amdgpu_irq_put(amdgpu_irq.c:541-562) only refcount-decrements and, at zero, calls.set(DISABLE)β it neither knows about nor waits for any work. A tree-wide grep confirms nocancel_work_sync/flush_workofflr_workanywhere (only mxgpu_ai.c:235/237/296/360 reference it).put_irqis called fromsoc15_common_hw_fini(soc15.c:843-846) β i.e., on every IP teardown: driver detach, suspend, and GPU recovery. -
What the surviving work does.
xgpu_ai_mailbox_flr_work(mxgpu_ai.c:235-271): - :237-238container_ofback toadevβ the very object whose lifetime is ending; - :250-252mutex_trylock(&adev->lock_reset),adev->in_gpu_reset = 1; - :254-260 up to 500 ms ofRREG32_NO_KIQmailbox peeks (AI_MAILBOX_POLL_FLR_TIMEDOUT = 500, mxgpu_ai.h); - :269-270amdgpu_device_gpu_recover(adev, NULL)β full reset attempt. -
The free. Driver detach:
amdgpu_driver_unload_kms(amdgpu_kms.c:75) βamdgpu_device_finiβ per-IPhw_finiβsoc15_common_hw_finiβput_irq(soc15.c:845) β¦ and afterwards the drm device is released (drm_dev_put(dev)at amdgpu_drv.c:974 on the probe-error path; the unload path drops the final reference the same way), which freesdev->dev_privateβ the kzalloc'dadev(amdgpu_kms.c:180-183). Aflr_workqueued beforeput_irq(the irq that scheduled it is now disabled, but the queue entry persists) can be running or get picked up by a worker at any point after that: lock/unlock on freedstruct lockmemory, MMIO through stale mappings,amdgpu_device_gpu_recoveron freedadevβ use-after-free. Even whenadevis not yet freed (recovery/suspend variant), the surviving work triggers an unintended secondamdgpu_device_gpu_recoverracing the fini/resume cycle (gpu_recovertakesadev->lock_resetat amdgpu_device.c:3388+, so the two recoveries serialize into a back-to-back double reset rather than a deadlock). -
The fix idiom is available and already used next door.
cancel_work_syncis implemented by the DFly shim (sys/dev/drm/include/linux/workqueue.h:131; linux_workqueue.c) and is already used at teardown foradev->reset_work(amdgpu_irq.c:279/310) β this is an omission, not a port limitation.
Honest boundary
Same guest as DF-4975: no AMD GPU (only QEMU std VGA chip=0x1111234), no
SR-IOV VF, amdgpu not compiled into the running kernel (env.txt in the
DF-4975 pack). The UAF additionally needs a host-timed FLR notification
just before teardown. Cannot be exercised from this guest β untested
(DB poc enum has no not_testable), with the defect established by the
trace above.
Exploit chain
None to uid0. The freed-object handler is not attacker-shaped (fixed function, fixed target object); realistic outcome is a panic or memory corruption during teardown β availability impact, host-timed. Rated Low.
Fix validation
fix_status: not_testable β fix.diff adds
cancel_work_sync(&adev->virt.flr_work) to xgpu_ai_mailbox_put_irq;
cannot be built/booted on this guest (hardware absent). The change
guarantees no flr_work remains queued/running once the mailbox irq
sources are torn down, which removes the hazard by construction.
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_testablefix.diff drains flr_work inside the teardown hook before the irq sources are dropped, removing the surviving-work hazard by construction; cannot be build-validated on this guest (no AMD hardware, amdgpu not compiled in).
findings/poc/DF-4976/fix.diff
Confirmed kernel references
- sys/dev/drm/amd/amdgpu/mxgpu_ai.c:365-369
- sys/dev/drm/amd/amdgpu/mxgpu_ai.c:287-296
- sys/dev/drm/amd/amdgpu/mxgpu_ai.c:235-271
- sys/dev/drm/amd/amdgpu/soc15.c:843-846
- sys/dev/drm/amd/amdgpu/amdgpu_irq.c:541-562
- sys/dev/drm/amd/amdgpu/amdgpu_irq.c:279
- sys/dev/drm/amd/amdgpu/amdgpu_kms.c:75
- sys/dev/drm/amd/amdgpu/amdgpu_kms.c:180
- sys/dev/drm/amd/amdgpu/amdgpu_drv.c:974
- sys/dev/drm/amd/amdgpu/amdgpu_virt.h:246
- sys/dev/drm/include/linux/workqueue.h:131
Detail
Evidence (decisive lines)
findings/poc/DF-4976/VERDICT.md (trace); decisive refs: mxgpu_ai.c:365-369 (no cancel), soc15.c:843-846 (put_irq at hw_fini), mxgpu_ai.c:287-296 (queue site), mxgpu_ai.c:235-271 (post-teardown touches), amdgpu_kms.c:180 + amdgpu_drv.c:974 (alloc/free of adev), workqueue.h:131 + amdgpu_irq.c:279 (idiom exists)
PoC changes
No runnable PoC exists: needs a Vega20-class SR-IOV VF whose PF times an FLR notification into a teardown window. Pack carries the lifetime trace instead.
Verified recommended fix
Add cancel_work_sync(&adev->virt.flr_work) to xgpu_ai_mailbox_put_irq() (see fix.diff).
Verdict
Real object-lifetime gap, proven by trace, not executable on the audit guest (no AMD GPU / no SR-IOV VF). xgpu_ai_mailbox_put_irq (mxgpu_ai.c:365-369) β called from soc15_common_hw_fini (soc15.c:843-846) on every detach/suspend/gpu-recovery teardown β drops the two irq sources but never cancel_work_sync/flush_work's adev->virt.flr_work; no other code drains it either. A flr_work queued by the RCV irq just before teardown (host/PF sends IDH_FLR_NOTIFICATION, mxgpu_ai.c:294-296) survives and then executes xgpu_ai_mailbox_flr_work (mxgpu_ai.c:235-271) against a device being torn down β mutex on adev->lock_reset, 500 ms of MMIO peeks, amdgpu_device_gpu_recover(adev) β and at driver detach continues past drm_dev_put into use-after-free of the kzalloc'd adev (amdgpu_kms.c:180, freed with dev_private). cancel_work_sync exists in the DFly shim (workqueue.h:131) and is already used for adev->reset_work at amdgpu_irq.c:279/310, so the omission is a genuine bug. Guest-local users can widen the recovery variant by forcing TDR via /dev/dri, but queueing itself is host-timed; rated Low like the family (DF-4755).
No comments yet.