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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4785 Β· 4 files
FileTypeDescriptionSize
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):

  1. 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;
  2. a GFX_CMD_ID_LOAD_ASD command with app_len == 0 and a NULL-derived payload is submitted to the PSP. When the PSP rejects it, the rejection is INVISIBLE: psp_cmd_submit_buf() checks resp.status only when ucode != NULL (amdgpu_psp.c:137-142 β€” the DF-4768 flaw). The driver then proceeds through psp_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.

VERDICT.md
↓ download raw

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). amdgpu never 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)

  1. psp_v11_0_init_microcode() (psp_v11_0.c:95-143) requests only "amdgpufw_vega20_sos"; it never assigns asd_fw, asd_start_addr, asd_ucode_size. adev is kzalloc'd β†’ both stay NULL/0.
  2. 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.bin and sets asd_ucode_size/asd_start_addr. So this is a real divergence of the DF port, not upstream behavior.
  3. psp_hw_start() (amdgpu_psp.c:291) calls psp_asd_load() for every PSP ASIC; psp_asd_load() (amdgpu_psp.c:254-255) does memcpy(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.
  4. PSP rejection of that command is swallowed because psp_cmd_submit_buf() gates the resp.status check on ucode != 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_testable
baseline no→ patch + rebuild →patched clean

fix.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
↓ fix.diffper-fix-DF-4785

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.