amdgpu_irq: amdgpu_irq_dispatch/amdgpu_irq_update call the optional .process/.set callbacks with no NULL guard while every sibling path validates them β dce_virtual registers a .process==NULL crtc_irq (num_types=7) at LEGACY/229, one stray IV away from a hardirq NULL-call panic
Summary
add_id only requires funcs!=NULL, so NULL optional callbacks register fine; the file guards them everywhere else (disable_all, get/put) but dispatch calls .process unguarded in hardirq and update calls .set unguarded - and dce_virtual's crtc_irq has .process==NULL with num_types=7 registered at LEGACY/229: one stray IV or firmware DISP_TIMER2 = NULL function-pointer call panic. The .set hole arms every user-forced GPU reset the day a num_types>0 source registers without .set. Full registry audit (61 sites) in the pack. NULL-guard fix compile-validated (-Werror).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4713 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.2 KB | β raw | |
| VERDICT.md | β | 7.0 KB | β raw | |
| fix.diff | β | 821 B | view raw | |
| build.log | β | 1.2 MB | β download | |
| env.txt | β | 856 B | view raw | |
| manifest.json | β | 1.5 KB | view raw | |
| verdict.json | β | 4.8 KB | view raw | |
| build.sh | β | 158 B | view raw |
DF-4713 β amdgpu_irq.c: unchecked optional-callback calls in dispatch/update
File: sys/dev/drm/amd/amdgpu/amdgpu_irq.c (pass 2, GLM 5.3)
Severity: Low Β· Confidence: likely (missing check certain; trigger
config/hardware-gated)
Build / Run
Hardware-gated (requires an AMD GPU for the amdgpu IRQ machinery to run; the
audit QEMU guest has only virtio-gpu β verified, see env.txt). No runtime PoC
per the DF-4519/DF-2201 HW-gated protocol. The fix is compile-validated
against the patched amdgpu_irq.c inside the full amdgpu KLD module build
(-Werror):
# guest (root):
cd /usr/src && patch -p1 < /tmp/df4713_fix.diff
cd /usr/src/sys/dev/drm/amd/amdgpu && make # MOD_BUILD_RC=0 (build.log)
What to look at
VERDICT.mdβ full narrative incl. the DF-4643 lock-type verification and all killed candidates (fini double-free, get/put races, registry bounds).build.logβ full untrimmed amdgpu module build log (MOD_BUILD_RC=0).fix.diffβ NULL guards forfuncs->process(dispatch) andfuncs->set(update), matching the file's own existing guard style.env.txtβ guest uname, no-AMD-GPU proof, shim citations.
DF-4713 VERDICT β amdgpu_irq.c unchecked optional callbacks (.process/.set)
Status: untested (runtime) β the missing checks are source-certain; the
NULL-call trigger requires an AMD GPU + dce_virtual display configuration the
audit guest cannot provide. Fix compile-validated in the full amdgpu module
build (-Werror, MOD_BUILD_RC=0).
Context: DF-4643 lock-type verification (requested by the orchestrator)
DF-4643 (dce_virtual .set β DragonFly hrtimer_cancel may sleep under
adev->irq.lock) β the lock's nature verified in THIS file:
amdgpu_irq.c:237:lockinit(&adev->irq.lock, "agail", 0, LK_CANRECURSE);β DragonFlystruct lock(sleepable lockmgr), NOT a raw spinlock.spinlock_tistypedef struct lock spinlock_t(sys/dev/drm/include/linux/spinlock_types.h:41), andspin_lock_irqsave(l, f)=local_irq_save(f)+lockmgr(l, LK_EXCLUSIVE)(sys/dev/drm/include/linux/spinlock.h:75-79).local_irq_disable()is onlycrit_enter()β a critical section, not a hard CPU IRQ disable (sys/dev/drm/include/linux/irqflags.h:34-38, with Dillon's comment explaining the deliberate choice).
So the "spinlock" is a sleepable lockmgr + crit section. The DF-4643 hazard
(callback sleeping while holding it inside the crit section) stands as filed;
that defect lives in the dce_virtual callback + shim, not re-filed here.
The LK_CANRECURSE at :237 exists precisely because .set callbacks
invoked under irq.lock (amdgpu_irq.c:139, :467) call back into irq code
that retakes it β consistent, no distinct defect in the init itself.
The defect (distinct, in THIS file)
The irq registry treats both callbacks as optional and validates them inconsistently:
amdgpu_irq_add_id(amdgpu_irq.c:359-360) only requiressource->funcs != NULLβ it happily registers sources withfuncs->set == NULLand/orfuncs->process == NULL.amdgpu_irq_disable_all(:134) checks!src->funcs->setbefore calling.amdgpu_irq_get/amdgpu_irq_put(:520-521, :550-551) check!src->funcs->setbefore proceeding.- But
amdgpu_irq_dispatch(:436) callsr = src->funcs->process(adev, src, entry);with no NULL check, in hardirq context; andamdgpu_irq_update(:467) callsr = src->funcs->set(adev, src, type, state);with no NULL check.
In-tree NULL providers exist:
dce_virtual_crtc_irq_funcs={ .set = dce_virtual_set_crtc_irq_state, .process = NULL }(dce_virtual.c:757-760), registered atdce_virtual.c:375on client LEGACY src_idVISLANDS30_IV_SRCID_SMU_DISP_TIMER2_TRIGGER(=229,ivsrcid/ivsrcid_vislands30.h:267) withcrtc_irq.num_types = AMDGPU_CRTC_IRQ_VBLANK6 + 1(=7 > 0,dce_virtual.c:762). If the IH ring ever delivers a LEGACY/229 vector on a dce_virtual system (amdgpu.virtual_display=1 on ASICs whose firmware/ display block still raises DISP_TIMER2, or a stray/stale IV after GPU reset),amdgpu_irq_dispatchperforms a NULL function-pointer call in hardirq β immediate kernel panic. Impact ceiling: local DoS.- The
sdma_v*_illegal_inst_irq_funcs(.processonly, no.setβ e.g.sdma_v4_0.c:1748-1750) currently carrynum_types == 0, soamdgpu_irq_gpu_reset_resume_helper'sk < src->num_typesloop (:493) never reachesamdgpu_irq_updatefor them β but the helper (:488-494) performs no.setcheck of its own, so one future registration withnum_types > 0and no.setturns every GPU reset (user-triggerable by hanging the GPU from an unprivileged DRI client) into a NULL-call panic on the recovery path (amdgpu_device.c:3287,3341).
The code already knows the callbacks are optional (three sibling call sites check); dispatch and update are the two holes.
Why Low
- No demonstrated HW path delivers src 229 to dispatch under dce_virtual on
this guest (no AMD GPU at all β
pciconfshows only virtio-gpu 1af4:1234). - The update hole is not reachable with the current registry (all
num_types > 0sources define.setβ verified across all 61amdgpu_irq_add_idcall sites). - Defense-in-depth fix is two trivial guards; matches upstream's own convention elsewhere in this file.
Killed candidates (negative results, this file)
- add/remove registry bounds:
add_idbounds-checks client_id/src_id (:353-357), rejects dup slots (:371-372);dispatchbounds-checks both ids before indexing (:409-417). No remove API exists in this snapshot. Clean. - num_types/enabled_types sizing: every IP sets
num_typesin early_init (e.g.sdma_v4_0.c:1276-1288) before sw_init'sadd_id(:1301+), soenabled_typesis always kcalloc'd with the final count (:374-383);get/put/update/enabledall re-checktype >= num_types(:517,:547,:578). Clean. - fini double-free: the double-registered static sources
(
gmc_v9_0.c:963/965vm_fault on VMC+UTCL2;sdma_v4_0.c:1301/1307trap_irq on SDMA0+SDMA1) all havedata == NULL, so thekfree(src)branch (:325-329) never fires for them;enabled_typesis freed once then NULL'd (second iteration kfree(NULL) β safe); the sources array itself is freed and NULL'd (:331-332) leaving no dangling registry pointer. Clean. - dispatch (hardirq) vs fini UAF:
amdgpu_irq_finifirst callsdrm_irq_uninstall(:302) βfree_irq(linux_irq.c:108-141) βbus_teardown_intr, which unregisters and synchronizes the handler before the kfree loop (:313-333) runs. Clean at teardown. - get/put refcount protocol: the re-check of
amdgpu_irq_enabled()under the lock insideamdgpu_irq_update(:462-467) makes concurrent get/put converge to the correct HW state; no underflow path reachable without a pre-existing driver bug (unbalanced put). - in-flight get/put vs fini kfree(enabled_types): theoretical window
(
irq_enabledcleared atdrm_irq.c:183before teardown completes); requires privileged device teardown racing an ioctl β upstream-identical; noted, not filed. virq[src_id]empty-then (:419-423):amdgpu_irq_create_mappingreturns 0 with its body#if 0'd (:692-699), sovirq[]is never set and the (empty) then-branch is dead. Benign.- hotplug work (:80-94): connector walk under
mode_config->mutex; hotplug_work flushed in fini (:309) before connector teardown. Clean. - irq_callback ring_index (:162-172): bounded by
ih->rptrmaintenance in amdgpu_ih.c (out of scope for this file). Clean. - MSI/MSI-X: DragonFly branch reads
_irq_type(:243); no vector-count math exists here to get wrong. Clean.
Recommended fix
fix.diff (this pack): NULL-guard funcs->process in dispatch and
funcs->set in update (releasing the lock before returning -EINVAL in the
update path), matching the guards disable_all/get/put already use.
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
fixedbuild-only validation (runtime hardware-gated): fix.diff applied to guest /usr/src; full amdgpu KLD module build (make -C /usr/src/sys/dev/drm/amd/amdgpu) completed MOD_BUILD_RC=0 with -Werror; patched amdgpu_irq.c compiled at build.log:119 and amdgpu.ko linked. Baseline/patched runtime not observable without an AMD GPU.
findings/poc/DF-4713/build.log (full untrimmed module build; decisive lines: 119 patched amdgpu_irq.c -Werror compile, 354 MOD_BUILD_RC=0)
Confirmed kernel references
- sys/dev/drm/amd/amdgpu/amdgpu_irq.c:359
- sys/dev/drm/amd/amdgpu/amdgpu_irq.c:436
- sys/dev/drm/amd/amdgpu/amdgpu_irq.c:467
- sys/dev/drm/amd/amdgpu/amdgpu_irq.c:134
- sys/dev/drm/amd/amdgpu/amdgpu_irq.c:488
- sys/dev/drm/amd/amdgpu/amdgpu_irq.c:520
- sys/dev/drm/amd/amdgpu/amdgpu_irq.c:550
- sys/dev/drm/amd/amdgpu/amdgpu_irq.c:237
- sys/dev/drm/amd/amdgpu/dce_virtual.c:375
- sys/dev/drm/amd/amdgpu/dce_virtual.c:757
- sys/dev/drm/amd/amdgpu/sdma_v4_0.c:1748
- sys/dev/drm/amd/amdgpu/amdgpu_device.c:3287
- sys/dev/drm/include/linux/spinlock.h:75
- sys/dev/drm/include/linux/irqflags.h:34
Detail
Exploit chain
[]
Evidence (decisive lines)
findings/poc/DF-4713/VERDICT.md (callback-contract matrix, DF-4643 lock verification, all killed candidates incl. fini double-free and teardown ordering); build.log (full amdgpu module build, MOD_BUILD_RC=0 at :354, patched amdgpu_irq.c -Werror at :119); env.txt (no AMD GPU: pciconf shows only virtio-gpu 1af4:1234); fix.diff (2 NULL guards)
PoC changes
n/a (no runtime PoC attempted; no AMD GPU on guest per DF-4519/DF-2201 hardware-gated protocol)
Verified recommended fix
NULL-guard the two optional-callback calls: skip with DRM_DEBUG when funcs->process is NULL in amdgpu_irq_dispatch; return -EINVAL (after unlocking) when funcs->set is NULL in amdgpu_irq_update - matching the guards already used by amdgpu_irq_disable_all/amdgpu_irq_get/amdgpu_irq_put.
Verdict
untested (runtime): amdgpu_irq_dispatch (amdgpu_irq.c:436) calls src->funcs->process and amdgpu_irq_update (amdgpu_irq.c:467) calls src->funcs->set without the NULL guards the file itself uses at :134 (disable_all), :520-521/:550-551 (get/put); amdgpu_irq_add_id (:359-360) only requires funcs != NULL, so .process-less/.set-less sources register fine. In-tree providers exist: dce_virtual_crtc_irq_funcs {.set=..., .process=NULL} (dce_virtual.c:757-760) registered with num_types=7 at LEGACY/229 (dce_virtual.c:375) - a stray/stale SMU_DISP_TIMER2 IV on a virtual-display config is a hardirq NULL-call panic; sdma illegal_inst sources have no .set but num_types==0 today, leaving amdgpu_irq_gpu_reset_resume_helper (:488-494 -> amdgpu_device.c:3287/3341) one registration away from a reset-path panic. Distinct from DF-4643 (that defect is the sleeping .set callback under the DragonFly sleepable-lockmgr shim - lock type verified here: spinlock_t == struct lock, spin_lock_irqsave == local_irq_save(crit_enter only) + lockmgr(LK_EXCLUSIVE), lockinit LK_CANRECURSE at amdgpu_irq.c:237). Fix compile-validated: full amdgpu KLD module build with -Werror, MOD_BUILD_RC=0, patched amdgpu_irq.c compiled at build.log:119.
No comments yet.