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

cik_sdma_soft_reset(): stale status variable β€” SDMA1-busy test reads mmSDMA0_F32_CNTL instead of mmSRBM_STATUS2, so a hung SDMA1 is never soft-reset

Summary

tmp holds the SRBM_STATUS2 snapshot; if the SDMA0 branch runs, tmp is reassigned to the F32_CNTL RMW value, and the following 'tmp & SDMA1_BUSY_MASK' tests an F32_CNTL value with a status mask. Whenever SDMA0 is busy (the typical both-engines-hung state), SDMA1 is never halted/reset - ring test fails, GPU unusable until reboot from an unpriv-triggerable illegal-instruction reset. Availability-only. Separate-status-variable fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4360 Β· 4 files
FileTypeDescriptionSize
README.md β€” 3.2 KB ↓ raw
VERDICT.md β€” 2.3 KB ↓ raw
verdict.json β€” 2.5 KB view raw
manifest.json β€” 652 B view raw

DF-4360 β€” cik_sdma_soft_reset(): stale tmp makes the SDMA1-busy test read the wrong register β†’ wedged engine survives reset

File: sys/dev/drm/amd/amdgpu/cik_sdma.c (pass 2) Severity: Low Β· Confidence: certain

What (static proof)

sys/dev/drm/amd/amdgpu/cik_sdma.c:1089  u32 tmp = RREG32(mmSRBM_STATUS2);
sys/dev/drm/amd/amdgpu/cik_sdma.c:1091  if (tmp & SRBM_STATUS2__SDMA_BUSY_MASK) {
sys/dev/drm/amd/amdgpu/cik_sdma.c:1093          tmp = RREG32(mmSDMA0_F32_CNTL + SDMA0_REGISTER_OFFSET);  /* tmp CLOBBERED */
sys/dev/drm/amd/amdgpu/cik_sdma.c:1094          tmp |= SDMA0_F32_CNTL__HALT_MASK;
sys/dev/drm/amd/amdgpu/cik_sdma.c:1095          WREG32(mmSDMA0_F32_CNTL + SDMA0_REGISTER_OFFSET, tmp);
sys/dev/drm/amd/amdgpu/cik_sdma.c:1096          srbm_soft_reset |= SRBM_SOFT_RESET__SOFT_RESET_SDMA_MASK;
sys/dev/drm/amd/amdgpu/cik_sdma.c:1097  }
sys/dev/drm/amd/amdgpu/cik_sdma.c:1098  if (tmp & SRBM_STATUS2__SDMA1_BUSY_MASK) {   /* tests F32_CNTL, NOT SRBM_STATUS2 */

The second if is meant to test the SDMA1 busy bit of mmSRBM_STATUS2, but when the SDMA0 branch executed, tmp now holds mmSDMA0_F32_CNTL | HALT. The SRBM_STATUS2__SDMA1_BUSY_MASK bit(s) in that value are meaningless (normally clear), so whenever SDMA0 is busy, SDMA1 is never halted/soft-reset, even if SDMA1 is also wedged β€” precisely the both-engines-hung state a reset is supposed to clear. (If the SDMA0 branch is NOT taken, tmp still holds SRBM_STATUS2 and the second test is accidentally correct β€” which is why this survives casual reading.)

Reachability / impact

Callers: cik_sdma_resume() (cik_sdma.c:1051 β€” S3/S4 resume) and the device-reset path armed by cik_sdma_process_illegal_inst_irq() β†’ schedule_work(&adev->reset_work) (cik_sdma.c:1217-1219), which an unprivileged DRI client can trigger by getting an illegal instruction into an SDMA command stream while both engines have work queued. Consequence of the missed reset: SDMA1 stays hung β†’ amdgpu_ring_test_ring() fails in cik_sdma_gfx_resume() (cik_sdma.c:502-508) β†’ hw_init error β†’ GPU unusable until reboot. Availability-only, hardware-specific (CIK ASIC required) β†’ Low / dos.

Fix

--- a/sys/dev/drm/amd/amdgpu/cik_sdma.c
+++ b/sys/dev/drm/amd/amdgpu/cik_sdma.c
@@ -1086,7 +1086,7 @@ static int cik_sdma_soft_reset(void *handle)
    u32 srbm_soft_reset = 0;
    struct amdgpu_device *adev = (struct amdgpu_device *)handle;
-   u32 tmp = RREG32(mmSRBM_STATUS2);
+   u32 status = RREG32(mmSRBM_STATUS2), tmp;

-   if (tmp & SRBM_STATUS2__SDMA_BUSY_MASK) {
+   if (status & SRBM_STATUS2__SDMA_BUSY_MASK) {
        /* sdma0 */
        tmp = RREG32(mmSDMA0_F32_CNTL + SDMA0_REGISTER_OFFSET);
@@ -1098,7 +1098,7 @@ static int cik_sdma_soft_reset(void *handle)
        srbm_soft_reset |= SRBM_SOFT_RESET__SOFT_RESET_SDMA_MASK;
    }
-   if (tmp & SRBM_STATUS2__SDMA1_BUSY_MASK) {
+   if (status & SRBM_STATUS2__SDMA1_BUSY_MASK) {
        /* sdma1 */
        tmp = RREG32(mmSDMA0_F32_CNTL + SDMA1_REGISTER_OFFSET);

Verification status

untested β€” requires CIK AMD hardware; the audit QEMU guest has no AMD GPU PCI device (pciconf -l), so cik_sdma_soft_reset cannot execute there. Static proof above is line-accurate; the stale-variable flow is unambiguous from source.

VERDICT.md
↓ download raw

VERDICT β€” DF-4360

status: untested (hardware-gated) Β· impact (demonstrated): none; expected class: local DoS on CIK hardware Β· confidence: certain (stale-variable flow unambiguous).

Reproduced?

No β€” needs a CIK AMD GPU. Guest (DragonFly 6.5-DEVELOPMENT x86_64, QEMU/KVM) has no 0x1002 PCI device; cik_sdma_soft_reset cannot execute there. Static proof:

  • cik_sdma.c:1089 tmp = RREG32(mmSRBM_STATUS2).
  • cik_sdma.c:1091-1097: if SDMA0 busy, tmp is REASSIGNED to RREG32(mmSDMA0_F32_CNTL + SDMA0_OFFSET) | HALT.
  • cik_sdma.c:1098: if (tmp & SRBM_STATUS2__SDMA1_BUSY_MASK) now tests an F32_CNTL value with the SDMA1 status mask β€” wrong register. In the both-engines-hung state the bit reads clear β‡’ SDMA1 never halted / SRBM_SOFT_RESET__SOFT_RESET_SDMA1_MASK never set β‡’ soft reset leaves SDMA1 wedged.
  • Consumers: cik_sdma_resume (cik_sdma.c:1051) on S3/S4 resume, and the reset armed by cik_sdma_process_illegal_inst_irq β†’ schedule_work(&adev->reset_work) (cik_sdma.c:1217-1219), reachable by an unprivileged DRI client that gets an illegal instruction into an SDMA stream. Result: amdgpu_ring_test_ring fails in cik_sdma_gfx_resume (cik_sdma.c:502-508) β‡’ hw_init error β‡’ GPU unusable until reboot.

Availability-only, hardware-specific β‡’ Low. No memory-corruption primitive (register write targets are fixed MMIO offsets selected by constants, not by tmp).

Exploit chain

Unprivileged DRI user on CIK hardware: wedge SDMA0+SDMA1 (queued work + illegal SDMA opcode) β†’ illegal-inst IRQ β†’ reset_work β†’ amdgpu device reset β†’ cik_sdma_resume β†’ soft_reset skips SDMA1 β†’ engine stays hung β†’ device reset fails. Ceiling: persistent GPU DoS. Not attempted on guest (no GPU).

Fix validation

not_testable β€” no CIK GPU to build/boot a patched kernel against. Fix keeps the status lvalue separate from the RMW scratch (see README diff); behavior for the non-SDMA0-busy path is bit-identical to today.

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_testable
baseline no→ patch + rebuild →patched clean

not_testable β€” no CIK hardware on the audit guest to compare baseline vs patched kernel

fix diff in README.md
per-fix-DF-4360

Confirmed kernel references

Detail

Exploit chain

(hardware-gated, not executed) unprivileged DRI client on CIK: queue work on both SDMA engines + inject illegal SDMA opcode -> illegal_inst IRQ -> schedule_work(reset_work) -> device reset -> cik_sdma_resume -> soft_reset skips SDMA1 (stale tmp) -> ring test fails -> device init error, GPU dead until reboot

Evidence (decisive lines)

README.md (annotated excerpt cik_sdma.c:1085-1104 + callers cik_sdma.c:502-508, 1047-1054, 1213-1220); VERDICT.md; guest pciconf shows no 0x1002 device

PoC changes

none β€” no PoC seed; static verification only (hardware-gated path)

Verified recommended fix

Keep the mmSRBM_STATUS2 snapshot in a separate variable (status) and test SDMA1_BUSY against it, not against the F32_CNTL RMW scratch

Verdict

cik_sdma_soft_reset() clobbers the mmSRBM_STATUS2 status variable with the mmSDMA0_F32_CNTL read inside the SDMA0 branch, so the following SDMA1_BUSY test examines the wrong register; whenever SDMA0 is busy the SDMA1 engine is never halted or soft-reset, leaving it wedged through resume and through the illegal-instruction reset path (cik_sdma_process_illegal_inst_irq -> reset_work) β€” a persistent local GPU denial of service on CIK hardware, availability-only. Runtime PoC impossible on the audit guest (no AMD PCI device); the stale-variable flow is unambiguous from source.