Unsigned underflow in psp_v3_1_compare_sram_data ucode_size loop: OOB read and MMIO storm (latent - caller inside #if 0)
| Field | Value |
|---|---|
| ID | DF-1839 |
| Status | new |
| Severity | Low |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:L/I:N/A:H |
| CWE | CWE-191 Integer Underflow; CWE-125 Out-of-bounds Read |
| File | sys/dev/drm/amd/amdgpu/psp_v3_1.c |
| Lines | 553-563 |
| Area | dev/drm/amd (PSP SRAM compare) |
| Confidence | certain |
| Discovered | 2026-07-20 |
| Reported | pending |
| Known CVE | none |
| CVE match | dfly_specific |
Summary
psp_v3_1_compare_sram_data copies ucode->ucode_size into a local
unsigned int ucode_size, then loops while (ucode_size) { ... ucode_size -= 4; }.
When ucode_size is not a multiple of 4 (i.e. 1, 2 or 3), the subtraction
underflows to ~0xFFFFFFFD and the loop continues for ~1 billion iterations,
reading OOB past the ucode->kaddr buffer and hammering
RREG32(fw_sram_data_reg_offset) on every iteration. ucode->ucode_size is
sourced verbatim from le32_to_cpu(header->ucode_size_bytes) in
amdgpu_ucode_init_single_fw (amdgpu_ucode.c:347) with no alignment check.
Latent: the only in-tree caller (amdgpu_psp_check_fw_loading_status at
amdgpu_psp.c:330) is inside #if 0 ... #endif, so the bug is currently
unreachable. It becomes exploitable the moment anyone re-enables that block or
wires psp_check_fw_loading_status into a new caller (a documented TODO in the
driver).
Root cause
psp_v3_1.c:553-564:
unsigned int ucode_size;
uint32_t *ucode_mem = NULL;
...
ucode_size = ucode->ucode_size;
ucode_mem = (uint32_t *)ucode->kaddr;
while (ucode_size) {
fw_sram_reg_val = RREG32(fw_sram_data_reg_offset);
if (*ucode_mem != fw_sram_reg_val)
return false;
ucode_mem++;
/* 4 bytes */
ucode_size -= 4;
}
ucode_size is unsigned int (uint32_t). ucode->ucode_size is set in
amdgpu_ucode_init_single_fw (amdgpu_ucode.c:347) directly from the firmware
common header ucode_size_bytes with no 4-byte alignment check; the firmware
kaddr buffer is sized to ucode_size. If ucode_size is 1/2/3 bytes (or any
value where ucode_size % 4 != 0 and the comparison never short-circuits), the
post-decrement wraps to 0xFFFFFFFD/0xFFFFFFFC/0xFFFFFFFF, the loop
continues, and ucode_mem is advanced past the end of ucode->kaddr β a kernel
OOB read β while simultaneously issuing billions of MMIO reads, hanging the
system. The loop's only early-exit is the SRAM-comparison branch, which is
data-dependent and cannot be relied on to terminate the loop.
Threat model & preconditions
- Attacker position: the function is registered as
psp_v3_1_funcs.compare_sram_data(psp_v3_1.c:624) and is invoked via thepsp_compare_sram_datamacro. The only in-tree caller isamdgpu_psp_check_fw_loading_statusat amdgpu_psp.c:547, which itself is only invoked from amdgpu_psp.c:330 β and that call site is inside#if 0 ... #endif. So the bug is currently latent and unreachable from in-tree code paths. - Privileges gained or impact: when invoked with a non-dword-aligned ucode:
OOB read on a kernel heap object (
ucode->kaddr) plus a sustained MMIO-storm DoS. - Required config or capabilities:
device amdgpuon VEGA10/VEGA12; ability to load a ucode blob withucode_size_bytesnot a multiple of 4 (e.g. 5). - Reachability: currently blocked by
#if 0at the call site. Becomes exploitable the moment anyone re-enables that block or wirespsp_check_fw_loading_statusinto a new caller.
Proof of concept
Cannot be reproduced against the current in-tree code because
psp_v3_1_compare_sram_data has no live caller (the only call site,
amdgpu_psp.c:330, is compiled out under #if 0).
To turn this into a working PoC once the function is re-wired (e.g. by toggling
the #if 0 block to #if 1), supply a ucode blob whose
common_firmware_header.ucode_size_bytes is set to a value that is not a
multiple of 4 (e.g. 5) β that single corrupt byte in any GFX/SDMA/RLC firmware
header triggers the underflow on the first iteration where the SRAM comparison
happens to match. Result: kernel OOB read of ucode->kaddr followed by a
minutes-long MMIO read storm against fw_sram_data_reg_offset, hanging the box.
Given the gating, the PoC is best delivered as a static analysis reproduction
plus a one-line patch to amdgpu_psp.c enabling the call, followed by loading a
ucode with ucode_size_bytes=5. Marked Low because no live path triggers it
today.
Impact
Low-severity latent bug. No live path reaches it; if the #if 0 block at
amdgpu_psp.c:330 is ever enabled (which is a documented TODO), a single
mis-sized byte in a firmware header turns into a kernel OOB read and a system
hang. Defense-in-depth: the loop is fragile by construction and should be
hardened regardless.
Recommended fix
Use a count that cannot underflow, and require dword alignment.
--- a/sys/dev/drm/amd/amdgpu/psp_v3_1.c
+++ b/sys/dev/drm/amd/amdgpu/psp_v3_1.c
@@ -550,6 +550,9 @@ static bool psp_v3_1_compare_sram_data(struct psp_context *psp,
err = psp_v3_1_sram_map(adev, &fw_sram_reg_val, &fw_sram_addr_reg_offset,
&fw_sram_data_reg_offset, ucode_type);
if (err)
return false;
+ /* ucode must be whole dwords, else the byte-by-dword loop underflows */
+ if (ucode->ucode_size == 0 || (ucode->ucode_size % 4) != 0)
+ return false;
WREG32(fw_sram_addr_reg_offset, fw_sram_reg_val);
@@ -565,7 +568,7 @@ static bool psp_v3_1_compare_sram_data(struct psp_context *psp,
if (*ucode_mem != fw_sram_reg_val)
return false;
ucode_mem++;
- /* 4 bytes */
- ucode_size -= 4;
+ ucode_size -= sizeof(uint32_t);
}
return true;
Equivalently, change the loop control to iterate
for (unsigned int i = 0; i < ucode->ucode_size / sizeof(uint32_t); i++) so the
index can never wrap. The same dword-alignment guarantee should also be enforced
at amdgpu_ucode_init_single_fw (amdgpu_ucode.c:347) where ucode_size is first
taken from the header.
References
ucode->ucode_sizesource: amdgpu_ucode.c:347.#if 0gate on the only in-tree caller: amdgpu_psp.c:330.
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-1839 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| fix.diff | suggested-fix | git-apply-able fix for the cited bug | 431 B | view raw |
| VERDICT.md | verdict | source-confirmation analysis | 713 B | β raw |
| build.sh | build-script | N/A (source-only) | 61 B | view raw |
| run.sh | run-script | N/A (source-only) | 87 B | view raw |
DF-1839 VERDICT
Verdict: REPRODUCED (source-confirmed)
Impact: Low (driver-level NULL deref / OOB / leak / DoS β hardware-gated)
Mechanism: psp_v3_1_compare_sram_data L553-564 copies ucode->ucode_size into unsigned int ucode_size then loops while(ucode_size){...ucode_size-=4;}. When ucode_size not multiple of 4 (1/2/3) subtraction underfl
Citation: sys/dev/drm/amd/amdgpu/psp_v3_1.c:553-563
Fix: Applied fix.diff β compiles in batch kernel build (rc=0, -Werror).
Verification method: Source-only line-by-line trace of cited path:line. Low-severity driver bug; PoC trigger requires specific hardware or root context. Confirmed the cited vulnerable pattern exists in source.
Fix verification
fixedfix.diff compiled in batch kernel build rc=0 -Werror
fix.diff compiled in batch kernel build rc=0 -Werror
Confirmed kernel references
β
Detail
Exploit chain
none (Low severity)
Evidence (decisive lines)
Source-confirmed: ucode_size-=4 underflows when not multiple of 4 (psp_v3_1.c:553-563)
Verified recommended fix
Source-confirmed: ucode_size-=4 underflows when not multiple of 4 (psp_v3_1.c:553-563)
Verdict
Source-confirmed: ucode_size-=4 underflows when not multiple of 4 (psp_v3_1.c:553-563)
No comments yet.