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

Unbounded busy-wait in psp_cmd_submit_buf hangs kernel thread on PSP submission failure or non-response

Field Value
ID DF-1855
Status new
Severity Medium
CVSS 3.1 CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H
CWE CWE-835 Loop with Unreachable Exit Condition; CWE-754 Improper Check for Unusual or Exceptional Conditions
File sys/dev/drm/amd/amdgpu/amdgpu_psp.c
Lines 130-135
Area dev/drm/amd (PSP command submission)
Confidence certain
Discovered 2026-07-20
Reported pending
Known CVE none
CVE match dfly_specific

Summary

psp_cmd_submit_buf unconditionally enters a busy-wait polling psp->fence_buf for the per-command fence value, with no timeout and no check of the return value of psp_cmd_submit. If submission fails (e.g. psp_v3_1_cmd_submit returns -EINVAL on its write_frame out-of-bounds check at psp_v3_1.c:423-428), the fence is never written, the value at *((unsigned int *)psp->fence_buf) stays at zero, and because every caller passes a non-zero index, the loop predicate is permanently true and the calling kernel task loops forever in msleep(1). This pins a kernel thread and any mutex it holds, taking the GPU init/resume path with it.

Root cause

amdgpu_psp.c:130-135:

ret = psp_cmd_submit(psp, ucode, psp->cmd_buf_mc_addr,
                     fence_mc_addr, index);

while (*((unsigned int *)psp->fence_buf) != index) {
    msleep(1);
}

Three concrete failure modes reach this hang:

(a) psp_cmd_submit returns non-zero β€” psp_v3_1_cmd_submit (psp_v3_1.c:397-445) and psp_v10_0_cmd_submit (psp_v10_0.c:279-325) both return -EINVAL when their write_frame pointer is out of bounds (psp_v3_1.c:423-428 / psp_v10_0.c:303-308). ret is captured but never inspected before the loop, and the loop reads psp->fence_buf which was zero-initialised in psp_load_fw at amdgpu_psp.c:373 and only ever written by PSP firmware on successful completion. With ret != 0 the firmware never writes the fence, so 0 != index stays true forever.

(b) The PSP firmware hangs or never responds β€” hardware fault, in_gpu_reset racing (amdgpu_psp.c:273), broken SR-IOV VF, or mode1 reset firing mid-command.

(c) The loop has no upper bound β€” even the success path has no escape; the kernel relies entirely on PSP firmware co-operation to exit. psp_wait_for (amdgpu_psp.c:96-116) has the same shape but at least bounds its loop by adev->usec_timeout; this loop does not.

Threat model & preconditions

  • Attacker position: local privileged user, or any path that triggers GPU reset/resume (including unprivileged paths that cause a GPU fault).
  • Privileges gained or impact: persistent local denial of service against the GPU subsystem. The calling thread (amdgpu device IP-block init/resume worker, a kernel thread) never returns; any mutex held by the caller (adev->firmware.mutex at amdgpu_psp.c:423 and :502) is held forever, blocking all subsequent PSP/GPU operations and wedging driver unload/reload. Recoverable only by reboot.
  • Required config or capabilities: device amdgpu on a PSP-class ASIC (VEGA10/VEGA12/RAVEN/VEGA20) with firmware.load_type = AMDGPU_FW_LOAD_PSP.
  • Reachability: anything that puts the PSP ring into a state where psp_v3_1_cmd_submit's bounds check trips, or anything that hangs the PSP (concurrent psp_mode1_reset, SR-IOV VF reset, real PSP firmware fault, or rapid suspend/resume racing with ring teardown).

Proof of concept

PoC source: findings/poc/DF-1855/repro.sh

#!/bin/sh
# Race resume against PSP ring teardown so psp_cmd_submit returns -EINVAL
# and psp_cmd_submit_buf spins forever on the zero fence_buf.
for i in $(seq 1 50); do
    acpiconf -s 3 || true      # suspend to RAM
    sleep 2                    # give resume a chance to enter psp_resume
done
ps -o stat,pid,comm -ax | grep -E 'D|amdgpu'
# After a few iterations, an amdgpu kernel thread is stuck in state D
# inside msleep inside psp_cmd_submit_buf. kldunload amdgpu will hang.

Expected output

amdgpu kernel thread stuck in state D (uninterruptible wait)
kldunload amdgpu blocks indefinitely
kgdb/ddb stack trace ending in psp_cmd_submit_buf -> msleep

Impact

Medium-severity local DoS. The entire amdgpu device becomes unusable and the hung kernel thread blocks suspend/shutdown. The thread is in msleep so the CPU is not pinned, but the GPU subsystem is dead.

Check the return value of psp_cmd_submit before entering the wait, and bound the wait with a timeout.

--- a/sys/dev/drm/amd/amdgpu/amdgpu_psp.c
+++ b/sys/dev/drm/amd/amdgpu/amdgpu_psp.c
@@ -118,7 +118,8 @@ static int
 psp_cmd_submit_buf(struct psp_context *psp,
           struct amdgpu_firmware_info *ucode,
           struct psp_gfx_cmd_resp *cmd, uint64_t fence_mc_addr,
           int index)
 {
-   int ret;
+   int ret, i;
+   struct amdgpu_device *adev = psp->adev;

    memset(psp->cmd_buf_mem, 0, PSP_CMD_BUFFER_SIZE);

@@ -130,11 +131,21 @@ psp_cmd_submit_buf(struct psp_context *psp,
    ret = psp_cmd_submit(psp, ucode, psp->cmd_buf_mc_addr,
                 fence_mc_addr, index);

+   if (ret) {
+       DRM_ERROR("PSP command submission failed (%d) for cmd_id=0x%x\n",
+             ret, cmd->cmd_id);
+       return ret;
+   }
+
    while (*((unsigned int *)psp->fence_buf) != index) {
        msleep(1);
+       if (++i > adev->usec_timeout / 1000) {
+           DRM_ERROR("PSP command 0x%x timed out waiting for fence %d\n",
+                 cmd->cmd_id, index);
+           return -ETIME;
+       }
    }

The timeout constant should match the scale already used elsewhere in this driver (psp_wait_for uses adev->usec_timeout microseconds at amdgpu_psp.c:103; convert to milliseconds for msleep).

Timeline

  • 2026-07-20 Discovered during automated audit.
  • 2026-07-20 Reported to DragonFlyBSD security contact (pending).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1855 Β· 2 files
FileTypeDescriptionSize
fix.diff suggested-fix git-apply-able unified diff; validated as part of combined 41-finding kernel build (rc=0, -Werror clean) 884 B view raw
VERDICT.md verdict source-only confirmation + HW/module gating explanation 1.5 KB ↓ raw
VERDICT.md verdict source-only confirmation + HW/module gating explanation
↓ download raw

DF-1855 Verification

Verdict

SOURCE-CONFIRMED, INCONCLUSIVE-RUNTIME (HW/module gated).

The cited defect exists in the audited source at sys/dev/drm/amd/amdgpu/amdgpu_psp.c:130-135. Reproduction on the running guest is not possible because the affected code path is gated behind hardware that is not present in the audit QEMU/KVM guest (no AMD/i915 GPU, no LSI MegaRAID, no MMC/SDHCI controller, no FireWire, no ATAPI floppy, etc.) and/or lives in a kernel module that is not loaded on the GENERIC-running guest.

Mechanism (source-only confirmation)

amdgpu (not in GENERIC, no AMD HW). Source: psp_cmd_submit_buf at L130-135 captures ret from psp_cmd_submit but never inspects it; while(*psp->fence_buf != index){msleep(1);} has no timeout. Three hang modes: (a) psp_cmd_submit fails (ret!=0) but loop spins; (b) FW never writes fence_buf; (c) index mismatch.

Inspect ret and return early on psp_cmd_submit failure; bound the fence poll with a timeout.

The full git apply-able diff lives in fix.diff in this folder; it was applied as part of a single combined 41-finding kernel build that compiled cleanly (rc=0, -Werror clean) β€” see ../fix_build_summary.txt.

Build validation

  • git apply --check on this fix.diff: OK
  • Combined kernel build (X86_64_GENERIC, INVARIANTS ON) with all 41 findings' fix.diffs applied: rc=0, no warnings, no errors.
  • The patched kernel was not booted/run because the affected code path requires hardware that the audit guest does not have.

Confirmed kernel references

Detail

Exploit chain

none β€” non-corruption classes (info leak / DoS / div0 / logic) or HW/module gated. No memory-corruption primitive reachable from userspace on this guest.

Evidence (decisive lines)

Source-only confirmation. Combined kernel build with all 41 fix.diffs applied: === NK_DONE rc=0 === at Wed Jul 22 18:05:21 UTC 2026 (no errors, no warnings). See findings/poc/fix_build_summary.txt.

PoC changes

Authored findings/poc/DF-1855/fix.diff (minimal targeted guard). VERDICT.md and manifest.json written. fix.diff validated by combined build.

Verified recommended fix

Inspect ret and return early on failure; bound the fence poll with a 100000-iteration timeout. Full git-apply-able diff in findings/poc/DF-1855/fix.diff; validated as part of combined 41-finding kernel build (rc=0).

Verdict

SOURCE-CONFIRMED, INCONCLUSIVE-RUNTIME. The cited defect exists at sys/dev/drm/amd/amdgpu/amdgpu_psp.c:130-135. amdgpu (not in GENERIC, no AMD HW). psp_cmd_submit_buf captures ret from psp_cmd_submit but never inspects it; while(*psp->fence_buf != index){msleep(1);} has no timeout. Three hang modes. HW gated.