psp_v11_0_init_microcode never loads ASD firmware for Vega20 β generic psp_asd_load memcpy's from NULL and submits a zero-length LOAD_ASD whose PSP rejection is silently swallowed
Summary
init_microcode loads only SOS, diverging from both siblings and upstream - psp_hw_start's ASIC-agnostic psp_asd_load executes memcpy(fw_pri_buf, NULL, 0) (UB) and submits a zero-length LOAD_ASD on every Vega20 boot; the PSP rejection is invisible because resp.status is only checked when ucode!=NULL (DF-4768 hitting v11 through the ASD path), and a fence-skipping PSP hangs the unbounded wait. Deterministic at init, no attacker input. hw-gated; source-proof complete. Mirror-psp_v3_1-ASD-block fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4785 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.8 KB | β raw | |
| VERDICT.md | β | 2.8 KB | β raw | |
| fix.diff | β | 1.7 KB | view raw | |
| verdict.json | β | 3.0 KB | view raw |
DF-4785 β psp_v11_0_init_microcode never loads ASD firmware for Vega20
What
psp_v11_0_init_microcode() (sys/dev/drm/amd/amdgpu/psp_v11_0.c:95-143)
requests and parses ONLY the SOS firmware. It never touches
adev->psp.asd_fw / asd_start_addr / asd_ucode_size, unlike both
siblings in the same tree β psp_v3_1_init_microcode()
(psp_v3_1.c:126-143, "amdgpufw_%s_asd") and psp_v10_0_init_microcode()
(psp_v10_0.c:126-140) β and unlike upstream Linux, whose psp_v11_0 loads
vega20_asd.bin (verified in mainline v5.2's psp_v11_0.c; upstream also
loads vega20_ta.bin later).
The generic PSP start path is ASIC-agnostic:
psp_hw_start() (amdgpu_psp.c:268-296) unconditionally calls
psp_asd_load() (amdgpu_psp.c:238-266), which does:
memset(psp->fw_pri_buf, 0, PSP_1_MEG);
memcpy(psp->fw_pri_buf, psp->asd_start_addr, psp->asd_ucode_size); /* NULL, 0 */
psp_prep_asd_cmd_buf(cmd, psp->fw_pri_mc_addr, psp->asd_shared_mc_addr,
0 /* asd_ucode_size */, PSP_ASD_SHARED_MEM_SIZE);
ret = psp_cmd_submit_buf(psp, NULL, cmd, psp->fence_buf_mc_addr, 2);
For Vega20 (CH_VEGA20 β psp_v11_0_set_psp_funcs, amdgpu_psp.c:61-62) this
executes on every PSP-mode boot with asd_start_addr == NULL and
asd_ucode_size == 0 (adev is zero-allocated):
memcpy(dst, NULL, 0)β undefined behavior per ISO C; benign on DragonFly's current x86_64 memcpy (zero-length is a no-op) but a latent portability/panic hazard;- a
GFX_CMD_ID_LOAD_ASDcommand withapp_len == 0and a NULL-derived payload is submitted to the PSP. When the PSP rejects it, the rejection is INVISIBLE:psp_cmd_submit_buf()checksresp.statusonly whenucode != NULL(amdgpu_psp.c:137-142 β the DF-4768 flaw). The driver then proceeds throughpsp_np_fw_load()believing ASD was loaded, with the PSP's ASD application never actually running.
Threat model
Not attacker-triggered: this fires deterministically at driver
initialization on Vega20 (AMD Radeon VII / MI50 / MI60 class) hardware
with amdgpu in PSP firmware-load mode. There is no user-controlled
input. Impact is robustness + silent-security-state divergence of the
secure-processor driver chain:
- UB memcpy with NULL source (latent panic on any memcpy implementation that pre-touches src, e.g. vectorized copies doing aligned prefetch β gcc-8-era DF is safe, this is defense-in-depth),
- swallowed PSP error β the "secure OS driver loaded" assumption of all later PSP commands is wrong on this ASIC (the DF-4768 silent-success class hitting v11 through the sysdrv/ASD path),
- interaction with the unbounded fence wait
(
while (*fence_buf != index) msleep(1), amdgpu_psp.c:133-135): if the PSP FW did not write the fence for the rejected command, boot would hang forever in-kernel; on real Vega20 PSP FW the fence is written, which is why this has gone unnoticed.
Severity Low (no attacker control, affected hardware barely supported on DF, memory-safety manifestation benign on current toolchain).
Fix
fix.diff (this pack): mirror psp_v3_1_init_microcode() β load
"amdgpufw_vega20_asd", validate, and populate asd_fw_version,
asd_feature_version, asd_ucode_size, asd_start_addr, plus release on
the error path and the MODULE_FIRMWARE declaration. Equivalent alternative
(one line, in amdgpu_psp.c): skip psp_asd_load() when psp->asd_fw ==
NULL for ASICs without ASD. The upstream-identical choice is loading ASD.
Verification status
untested β not testable on this guest: the audit QEMU guest has no AMD
GPU at all (pciconf shows only Intel/QEMU virtual PCI: hostb, isab, atapci,
vgapci0 chip=0x11111234, virtio; no AMD display device), so the amdgpu/PSP
probe never runs and psp_v11_0_* is unreachable. The divergence itself is
fully proven from source (siblings + upstream comparison); no PoC can
execute the path without Vega20 hardware.
DF-4785 β VERDICT
Finding: psp_v11_0.c never initializes the ASD firmware fields for Vega20 while the shared amdgpu_psp.c start path unconditionally consumes them (memcpy from NULL, zero-length LOAD_ASD with swallowed error status).
Reproduced? NO β untestable on this guest (no AMD hardware)
- Guest
pciconf -l(evidence in README/notes): only Intel/QEMU virtual PCI devices (vgapci0 chip=0x11111234 = QEMU/bochs VGA, virtio NIC/blk).amdgpunever attaches;psp_v11_0_set_psp_funcs()(only reached for CHIP_VEGA20 via amdgpu_psp.c:61-62) is unreachable at runtime. kldloading amdgpu.ko cannot change that β there is no AMD PCI device to probe. - No PoC was therefore built/run; per contract this Low-severity finding is skipped for Phase V and recorded with the closest allowed status ("untested").
Source-level proof (why the finding itself is certain)
psp_v11_0_init_microcode()(psp_v11_0.c:95-143) requests only "amdgpufw_vega20_sos"; it never assignsasd_fw,asd_start_addr,asd_ucode_size. adev is kzalloc'd β both stay NULL/0.- Sibling implementations in the same tree DO load ASD: psp_v3_1.c:126-143
("amdgpufw_%s_asd"), psp_v10_0.c:126-143. Upstream mainline (v5.2
psp_v11_0.c, verified via raw.githubusercontent.com) also loads
vega20_asd.binand sets asd_ucode_size/asd_start_addr. So this is a real divergence of the DF port, not upstream behavior. psp_hw_start()(amdgpu_psp.c:291) callspsp_asd_load()for every PSP ASIC;psp_asd_load()(amdgpu_psp.c:254-255) doesmemcpy(psp->fw_pri_buf, psp->asd_start_addr, psp->asd_ucode_size)= memcpy(p, NULL, 0) β UB; and submits GFX_CMD_ID_LOAD_ASD with app_len = 0.- PSP rejection of that command is swallowed because
psp_cmd_submit_buf()gates the resp.status check onucode != NULL(amdgpu_psp.c:137-142, the DF-4768 finding) β the TMR/ASD-for-v11 instance the pass-2 brief asked to check.
Impact ceiling: latent boot-time panic (UB memcpy), silent PSP error state, potential infinite fence-wait hang β all on Vega20-class hardware only, no attacker control. Severity Low, confidence certain (divergence provable from source), exploitability speculative/none.
Fix
fix.diff mirrors psp_v3_1's ASD load block into psp_v11_0_init_microcode
(+MODULE_FIRMWARE, +release on error). Not runtime-validated (no hardware);
compiles by inspection against the same headers/types used by psp_v3_1.c
(identical field names and firmware-header types).
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.
Fix verification
not_testablefix.diff authored (mirrors psp_v3_1 ASD block; git-apply clean vs sys/) but cannot be runtime-validated without Vega20 hardware
findings/poc/DF-4785/fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
Source divergence chain: psp_v11_0.c:95-143 (sos only) vs psp_v3_1.c:126-143 and psp_v10_0.c:126-143 (asd loaded) vs upstream v5.2 psp_v11_0.c (asd loaded); consumption: amdgpu_psp.c:291 (psp_hw_start -> psp_asd_load), amdgpu_psp.c:254-255 (memcpy NULL/0 + zero-len LOAD_ASD), amdgpu_psp.c:137-142 (resp.status ignored when ucode==NULL). Guest untestability: pciconf -l shows vgapci0 chip=0x11111234 (QEMU/bochs) and virtio devices only.
PoC changes
No PoC authored: the finding requires Vega20 (AMD) hardware for any runtime manifestation; the guest has none (verified via pciconf).
Verified recommended fix
Load amdgpufw_vega20_asd in psp_v11_0_init_microcode mirroring psp_v3_1 (see fix.diff), or alternatively skip psp_asd_load when psp->asd_fw == NULL
Verdict
psp_v11_0_init_microcode (psp_v11_0.c:95-143) loads only the SOS firmware for Vega20; it never sets asd_fw/asd_start_addr/asd_ucode_size, diverging from both in-tree siblings (psp_v3_1.c:126-143, psp_v10_0.c:126-143) and from upstream mainline (v5.2 psp_v11_0.c loads vega20_asd.bin). The ASIC-agnostic psp_hw_start -> psp_asd_load (amdgpu_psp.c:291, 238-266) then executes memcpy(fw_pri_buf, NULL, 0) (UB, latent panic) and submits GFX_CMD_ID_LOAD_ASD with app_len=0 whose PSP rejection is swallowed by the ucode==NULL resp.status ignore (amdgpu_psp.c:137-142, DF-4768 class). Not runtime-verifiable on the audit guest: pciconf shows only QEMU/Intel virtual PCI (no AMD GPU), so the amdgpu PSP path is unreachable; severity Low with no attacker control. fix.diff mirrors the psp_v3_1 ASD load block; not runtime-validated for the same hardware reason.
No comments yet.