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.mutexat 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 amdgpuon a PSP-class ASIC (VEGA10/VEGA12/RAVEN/VEGA20) withfirmware.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 (concurrentpsp_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.
Recommended fix
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)
PoC verification
Evidence pack
findings/poc/DF-1855 Β· 2 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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.
Recommended fix
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 --checkon 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
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- a
- m
- d
- /
- a
- m
- d
- g
- p
- u
- /
- a
- m
- d
- g
- p
- u
- _
- p
- s
- p
- .
- c
- :
- 1
- 3
- 0
- -
- 1
- 3
- 5
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.
No comments yet.