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

DragonFly port removed upstream's in_interrupt() guard from amdgpu_virt_kiq_rreg/wreg: unconditional console kprintf per SR-IOV MMIO access and msleep() from hardirq context on KIQ fence timeout

Summary

The port #if 0'd the interrupt-context half of the timeout guard (leaving a kprintf TODO) while this port - unlike Linux - runs IH processing in hardirq: every KIQ-mediated MMIO access (including per-interrupt from cz_ih_set_rptr) prints a console line, and a stalled KIQ ring takes 5ms busy-poll + msleep x80 FROM HARDIRQ - illegal sleep that panics INVARIANTS and corrupts the scheduler otherwise. DF-port-specific (upstream bails before msleep). Guest DoS on SR-IOV VF; hw-gated. Restore-the-guard-with-gd_intr_nesting_level fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4915 Β· 6 files
FileTypeDescriptionSize
README.md β€” 2.3 KB ↓ raw
VERDICT.md β€” 5.7 KB ↓ raw
trigger.md β€” 1.8 KB ↓ raw
env.txt β€” 178 B view raw
verdict.json β€” 3.4 KB view raw
manifest.json β€” 1.1 KB view raw

DF-4915 β€” amdgpu_virt_kiq_rreg/wreg: DF port deleted upstream's in_interrupt() guard β†’ msleep() from hardirq + per-access console kprintf

Build / Run / Expected

There is no buildable PoC on this guest: the trigger requires an AMD GPU running as an SR-IOV VF (guest assigned a virtual function of a Tonga/ Fiji/Vega-class device). The audit QEMU guest has no AMD PCI device at all (only i440fx + virtio + bochs VGA β€” see env.txt), so amdgpu never instantiates an amdgpu_device and the code path is unreachable at runtime.

To reproduce on hardware that has the device:

  1. On a host with an SR-IOV-capable AMD GPU (Tonga/Fiji/Polaris/Vega10), enable VFs (echo N > /sys/class/drm/card0/device/sriov_numvfs on Linux with the GIM/amdgpu PF driver, or vgt/SR-IOV via the vendor stack).
  2. Pass one VF to a DragonFly guest (pptdevs=0:x:y.z), boot the guest, kldload amdgpu.
  3. Observe (no fault injection needed): every MMIO access routed through the KIQ under AMDGPU_SRIOV_CAPS_RUNTIME prints amdgpu_virt_kiq_rreg: implement in_interrupt() function (or the _wreg twin) to the console β€” including from the GPU hard-IRQ handler on CZ-class parts where cz_ih_set_rptr() does an unconditional WREG32(mmIH_RB_RPTR) per interrupt. This is the guaranteed-by-construction console flood.
  4. Fault injection (panic leg): stall the KIQ ring (host-side VF FLR, or GPU hang) so amdgpu_fence_wait_polling() never sees the fence. The next GPU interrupt on a CZ VF (or a Tonga VF with the IH RB_OVERFLOW bit set, which takes RREG32(mmIH_RB_CNTL) in tonga_ih_get_wptr) enters amdgpu_virt_kiq_wreg from hardirq context, times out after 5 ms of busy-wait inside the hardirq, then executes msleep(5) from hardirq context β†’ illegal sleep in interrupt context (scheduler corruption / panic: ... on DEBUG kernels), retried up to 80Γ—.

Success criterion: step 3 alone (console line per KIQ access) proves the divergence; step 4 proves the illegal-sleep panic class.

Files in this pack

  • VERDICT.md β€” full narrative, path:line evidence, upstream diff.
  • trigger.md β€” the trigger procedure above, machine-copyable.
  • env.txt β€” guest hardware inventory proving absence of AMD GPU.
  • manifest.json, verdict.json β€” machine records.
VERDICT.md
↓ download raw

DF-4915 β€” VERDICT

Status: not_reproduced (hardware-gated β€” no AMD GPU / SR-IOV VF in the audit guest); the code divergence itself is certain and proven by source + upstream comparison.

What the finding is

sys/dev/drm/amd/amdgpu/amdgpu_virt.c ports Linux v4.19's amdgpu_virt_kiq_rreg() / amdgpu_virt_kiq_wreg(), but the DragonFly port #if 0'd out upstream's interrupt-context guard:

  • amdgpu_virt.c:162-168 (rreg) and amdgpu_virt.c:216-222 (wreg):
#if 0
    if (r < 1 && (adev->in_gpu_reset || in_interrupt()))
        goto failed_kiq_read;

    if (in_interrupt())
        might_sleep();
#endif
    kprintf("amdgpu_virt_kiq_rreg: implement in_interrupt() function\n");
    if (r < 1 && (adev->in_gpu_reset))
        goto failed_kiq_read;

    while (r < 1 && cnt++ < MAX_KIQ_REG_TRY) {
        msleep(MAX_KIQ_REG_BAILOUT_INTERVAL);
        ...

Upstream v4.19 (torvalds/linux) in the same spot:

    if (r < 1 && (adev->in_gpu_reset || in_interrupt()))
        goto failed_kiq_read;
    ...
    while (r < 1 && cnt++ < MAX_KIQ_REG_TRY) {
        msleep(MAX_KIQ_REG_BAILOUT_INTERVAL);

i.e. upstream never reaches msleep() from interrupt context (it bails to failed_kiq_read/write first, as the retained comment at amdgpu_virt.c:154-161 explicitly documents: "also don't wait anymore for IRQ context"). The DF port dropped that half of the condition, left the comment, and printed a TODO instead.

Why the guard matters on this port β€” the hardirq path is real

  1. GPU interrupts are serviced in hardirq context on this port: amdgpu_irq_handler() (sys/dev/drm/amd/amdgpu/amdgpu_irq.c:189-197) calls amdgpu_ih_process() directly β€” no threaded-IRQ/taskqueue deferral.
  2. amdgpu_ih_process() finishes with amdgpu_ih_set_rptr() (sys/dev/drm/amd/amdgpu/amdgpu_ih.c:159), and on Carrizo VI parts cz_ih_set_rptr() is an unconditional WREG32(mmIH_RB_RPTR, ...) (sys/dev/drm/amd/amdgpu/cz_ih.c:276-279). On Tonga the doorbell path avoids it, but the IH ring-overflow branch of tonga_ih_get_wptr() does RREG32(mmIH_RB_CNTL) + WREG32 (tonga_ih.c:213-216) β€” and the RB_OVERFLOW bit arrives in the GPU-written wptr writeback.
  3. WREG32/RREG32 expand to amdgpu_mm_wreg/amdgpu_mm_rreg (amdgpu.h:1099-1102), and amdgpu_mm_rreg() dispatches to amdgpu_virt_kiq_rreg() whenever amdgpu_sriov_runtime(adev) is set and the accessor is not AMDGPU_REGS_NO_KIQ (sys/dev/drm/amd/amdgpu/amdgpu_device.c:131-132) β€” i.e. for every plain register access in a running SR-IOV VF guest.

Consequences, in order of certainty:

  • Certain, no fault needed: the kprintf at amdgpu_virt.c:169 / :223 fires on every KIQ-mediated register access in every SR-IOV VF guest, including from hardirq context at interrupt rate. Console/log flood + serialization of every MMIO access.
  • Certain from source, needs a stalled KIQ: the first amdgpu_fence_wait_polling(..., MAX_KIQ_REG_WAIT) (amdgpu_virt.c:152, MAX_KIQ_REG_WAIT = 5000 Β΅s, amdgpu.h:239) busy-polls up to 5 ms inside the hardirq; if the fence still hasn't completed and the device is not in in_gpu_reset, the port falls into msleep(5) (amdgpu_virt.c:175, amdgpu_virt.c:229) β€” an illegal sleep from hardirq context (msleep β†’ tsleep, sys/dev/drm/include/linux/delay.h:33-39), repeated up to MAX_KIQ_REG_TRY = 80 times (amdgpu.h:241). Sleeping from a DF hardirq corrupts the LWKT scheduler state (context switch away from an interrupt frame); on INVARIANTS kernels this is a panic, on others it is undefined scheduler behavior.

Why not reproduced on the audit guest

The audit QEMU guest has no AMD PCI device (env.txt: zero vendor-0x1002 functions; VGA is bochs 0x1234, NIC is virtio). amdgpu.ko exists but no amdgpu_device is ever instantiated, adev->virt.caps never gains AMDGPU_SRIOV_CAPS_RUNTIME, and amdgpu_virt_kiq_rreg/wreg are never invoked. The trigger is therefore hardware-gated; per the audit's honest classification this maps to not_reproduced with impact none on this guest, while the code-level divergence is certain by direct comparison with upstream v4.19 (fetched and archived in this pack's analysis, quoted verbatim above).

Exploit chain

None β€” denial-of-service class only (hardirq stall β‰₯5 ms per access, console flood, illegal-sleep panic on stalled KIQ). No memory-safety primitive.

Restore upstream's interrupt-context semantics using DragonFly's mycpu->gd_intr_nesting_level test (the port already uses this idiom elsewhere), and delete the unconditional kprintf:

--- a/sys/dev/drm/amd/amdgpu/amdgpu_virt.c
+++ b/sys/dev/drm/amd/amdgpu/amdgpu_virt.c
@@ -159,11 +159,10 @@ uint32_t amdgpu_virt_kiq_rreg(struct amdgpu_device *adev, uint32_t reg)
     * also don't wait anymore for IRQ context
     * */
-#if 0
-   if (r < 1 && (adev->in_gpu_reset || in_interrupt()))
+#define in_interrupt() (mycpu->gd_intr_nesting_level != 0)
+   if (r < 1 && (adev->in_gpu_reset || in_interrupt()))
        goto failed_kiq_read;
-
-   if (in_interrupt())
-       might_sleep();
-#endif
-   kprintf("amdgpu_virt_kiq_rreg: implement in_interrupt() function\n");
+#undef in_interrupt
    if (r < 1 && (adev->in_gpu_reset))
        goto failed_kiq_read;

(the same change in amdgpu_virt_kiq_wreg). If a transition marker is wanted, replace the kprintf with a one-shot bootverbose-gated note.

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.)

trigger.md
↓ download raw

DF-4915 β€” trigger procedure (SR-IOV VF AMD GPU required)

Prerequisite hardware: AMD GPU with SR-IOV (Tonga/Fiji/Polaris/Vega10) on the host, one VF passed through to a DragonFly 6.x guest (e.g. pptdevs=5:0.0 in /boot/loader.conf), amdgpu.ko loadable in the guest.

Leg-1 β€” guaranteed console flood (no fault injection)

kldload amdgpu            # in the VF guest
dmesg | tail              # after init completes, full-access is released
# any runtime driver activity that touches MMIO now routes via KIQ:
tail -f /var/log/messages

Expected: amdgpu_virt_kiq_rreg: implement in_interrupt() function and/or the _wreg twin printed for every KIQ-mediated access β€” including bursts aligned with GPU interrupts (CZ-class parts: one line per interrupt via cz_ih_set_rptr's unconditional WREG32(mmIH_RB_RPTR)).

Leg-2 β€” illegal msleep() from hardirq (needs stalled KIQ)

  1. Host: trigger a VF function-level reset while the guest driver is at runtime (AMDGPU_SRIOV_CAPS_RUNTIME set), or otherwise stall the KIQ ring so amdgpu_fence_wait_polling() times out.
  2. Guest: generate GPU interrupts (any GEM workload; on Tonga an IH ring overflow also works β€” the RB_OVERFLOW path in tonga_ih_get_wptr does a plain RREG32(mmIH_RB_CNTL) from the hardirq).
  3. Expected: guest hardirq spins 5 ms in amdgpu_fence_wait_polling, then enters msleep(5) from interrupt context (amdgpu_virt.c:175/:229) β€” INVARIANTS kernels panic; others corrupt scheduler state / wedge the CPU in the interrupt path for up to ~80 x (5 ms poll + 5 ms sleep).

Why it cannot run in the audit guest

pciconf -l shows zero vendor-0x1002 functions (see env.txt); amdgpu.ko is present but no device binds, so amdgpu_virt_kiq_rreg/wreg are never called. Classification on this guest: not_reproduced (hardware absent).

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

Fix could not be validated: no SR-IOV-capable AMD GPU in the audit guest, so neither baseline nor patched behavior is reachable; the proposed diff restores upstream v4.19 semantics verbatim.

VERDICT.md 'Recommended fix' section contains the git-apply-able diff against amdgpu_virt.c.
per-fix-DF-4915

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md (upstream-vs-DF diff of the guard, hardirq path chain with path:line); trigger.md (2-leg trigger for SR-IOV hardware); env.txt (pciconf: zero vendor-0x1002 devices on guest)

PoC changes

No runnable PoC exists for this guest (hardware-gated); pack documents the exact hardware trigger procedure and the source-level proof instead of a C reproducer.

Verified recommended fix

Restore the interrupt-context early-out using mycpu->gd_intr_nesting_level != 0 in both amdgpu_virt_kiq_rreg and amdgpu_virt_kiq_wreg and drop the unconditional kprintf (see VERDICT.md diff).

Verdict

The DragonFly port of amdgpu_virt.c #if 0'd out upstream's in_interrupt() guard in amdgpu_virt_kiq_rreg/wreg (amdgpu_virt.c:162-168, 216-222) while this port runs GPU IH processing directly in hardirq (amdgpu_irq.c:189-197 -> amdgpu_ih.c:159; cz_ih.c:276-279 does an unconditional WREG32 per interrupt; tonga_ih.c:213-216 on IH overflow). Result on a running SR-IOV VF guest: an unconditional kprintf per KIQ-mediated MMIO access (console flood, incl. hardirq context) and, when the KIQ fence times out, msleep(5) from hardirq context (amdgpu_virt.c:175/229) x80 iterations - an illegal sleep from interrupt context that panics/corrupts the scheduler. Not reproduced on the audit QEMU guest because no AMD PCI device exists (env.txt), so amdgpu never binds and the functions are unreachable; the code-level divergence vs upstream v4.19 is certain (verbatim comparison archived in VERDICT.md).