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

cik_sdma_init_microcode() ignores amdgpu_ucode_validate() failure for all but the last SDMA instance β€” invalid firmware accepted (door to the DF-1553 sink that a DF-1553 fix does not close)

Summary

Inside the per-instance firmware loop, request_firmware's error is checked but amdgpu_ucode_validate's is assigned and never branched on; only the final iteration's value survives. With 2 instances, an instance-0 blob that fails the sanity gate while instance-1 validates yields err==0 at exit - the unvalidated blob stays loaded and is later consumed by load_microcode (OOB read into GPU registers/dmesg). Distinct defect from DF-1553: different function (init vs load), different root cause (dropped error propagation), different fix. One-line if(err)goto out fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4359 Β· 4 files
FileTypeDescriptionSize
README.md β€” 3.4 KB ↓ raw
VERDICT.md β€” 2.5 KB ↓ raw
verdict.json β€” 2.3 KB view raw
manifest.json β€” 558 B view raw

DF-4359 β€” cik_sdma_init_microcode() swallows amdgpu_ucode_validate() failure for all but the last SDMA instance

File: sys/dev/drm/amd/amdgpu/cik_sdma.c (pass 2) Severity: Low Β· Confidence: certain Β· Bucket: base:drm/amdgpu (adjacent to DF-1553 β€” DISTINCT defect, see below)

What (static proof, no runtime needed)

sys/dev/drm/amd/amdgpu/cik_sdma.c:133  for (i = 0; i < adev->sdma.num_instances; i++) {
sys/dev/drm/amd/amdgpu/cik_sdma.c:138      err = request_firmware(&adev->sdma.instance[i].fw, fw_name, adev->dev);
sys/dev/drm/amd/amdgpu/cik_sdma.c:139      if (err)
sys/dev/drm/amd/amdgpu/cik_sdma.c:140          goto out;                       /* request_firmware IS checked */
sys/dev/drm/amd/amdgpu/cik_sdma.c:141      err = amdgpu_ucode_validate(adev->sdma.instance[i].fw);
                                            /* <-- result NEVER branched on inside the loop */
sys/dev/drm/amd/amdgpu/cik_sdma.c:142  }
sys/dev/drm/amd/amdgpu/cik_sdma.c:143  out:
sys/dev/drm/amd/amdgpu/cik_sdma.c:144      if (err) { ... release all ... }

num_instances == 2 (set in cik_sdma_early_init, cik_sdma.c:950). If instance 0's firmware blob fails validation (err = -EINVAL) but instance 1's passes (err = 0), the loop exits with err == 0: cik_sdma_sw_init() returns success and the unvalidated instance-0 firmware stays in adev->sdma.instance[0].fw.

amdgpu_ucode_validate() (sys/dev/drm/amd/amdgpu/amdgpu_ucode.c:251-260) is the only sanity gate on the blob:

if (fw->datasize == le32_to_cpu(hdr->size_bytes))
        return 0;
return -EINVAL;

So a corrupt/inconsistent instance-0 blob (header size_bytes != datasize) β€” the very class validate exists to reject β€” is accepted. Its garbage header is then consumed by cik_sdma_load_microcode() (cik_sdma.c:552-565: fw_size = ucode_size_bytes/4, fw_data = fw->data + ucode_array_offset_bytes, 64 KB of WREG32(..., le32_to_cpup(fw_data++))) and amdgpu_ucode_print_sdma_hdr() β€” i.e. the exact DF-1553 sink is reachable through a door that DF-1553's own fix does not close.

Why distinct from DF-1553

DF-1553 = load_microcode trusts header offset/size fields (missing bounds check in the consumer). DF-4359 = init_microcode drops the error of the validation producer, so a firmware image that fails even the existing weak check is admitted. Different function, different line, different fix (error propagation, not bounds math). Both must be fixed.

Threat model

Firmware files (amdgpufw_<asic>_sdma{,1}) come from the root-owned firmware path; the attacker must already control firmware content (root or supply-chain). Impact ceiling: out-of-bounds kernel read streamed into GPU registers / dmesg via the DF-1553 sink. Same trust model that graded DF-1553/DF-1673 Low.

Fix

--- a/sys/dev/drm/amd/amdgpu/cik_sdma.c
+++ b/sys/dev/drm/amd/amdgpu/cik_sdma.c
@@ -138,6 +138,8 @@ static int cik_sdma_init_microcode(struct amdgpu_device *adev)
        err = request_firmware(&adev->sdma.instance[i].fw, fw_name, adev->dev);
        if (err)
            goto out;
        err = amdgpu_ucode_validate(adev->sdma.instance[i].fw);
+       if (err)
+           goto out;
    }
 out:

Verification status

untested β€” requires a CIK AMD GPU (Bonaire/Hawaii/Kaveri/Kabini/Mullins) to exercise the amdgpu cik_sdma IP block; the audit guest (DragonFly 6.5-DEVELOPMENT x86_64 under QEMU/KVM) has no AMD PCI device (pciconf -l shows none) so the code path cannot execute at runtime there. Static proof above is line-accurate and complete.

VERDICT.md
↓ download raw

VERDICT β€” DF-4359

status: untested (runtime PoC impossible on the audit guest β€” hardware-gated) Β· impact (demonstrated): none Β· confidence: certain (static proof).

Reproduced?

No β€” and it cannot be on this guest: exercising the path requires a CIK AMD ASIC (Bonaire/Hawaii/Kaveri/Kabini/Mullins) so the amdgpu cik_sdma IP block instantiates. Guest check (2026-09-19): pciconf -l on DragonFly 6.5-DEVELOPMENT x86_64 (QEMU/KVM) shows no AMD (0x1002) PCI device; cik_sdma_init_microcode is unreachable at runtime. No kernel build/test was performed; this is a pure code-correctness defect proven from source:

  • cik_sdma.c:141 β€” err = amdgpu_ucode_validate(...) result is never branched on inside the loop; only the last iteration's result reaches the out: handler.
  • With num_instances == 2 (cik_sdma.c:950): instance-0 validate failure (-EINVAL) is overwritten by instance-1 success (0) β†’ function returns 0 keeping an unvalidated blob in adev->sdma.instance[0].fw.
  • amdgpu_ucode.c:251-260 β€” validate is the only gate (datasize == hdr->size_bytes); a blob failing it is exactly one whose header is internally inconsistent.
  • Downstream sink (already known as DF-1553): cik_sdma.c:552-565 consumes ucode_size_bytes / ucode_array_offset_bytes with no bounds check β†’ OOB kernel read streamed into GPU registers via WREG32 loop; amdgpu_ucode_print_sdma_hdr formats the garbage header.

DF-4359 is DISTINCT from DF-1553: different function (init vs load), different root cause (dropped error propagation vs missing bounds math), different fix. Fixing DF-1553 alone leaves DF-4359 admitting firmware that even the weak check rejects.

Exploit chain

Not applicable: firmware placement requires root (same trust model that graded DF-1553/DF-1673 Low). Ceiling: OOB kernel-memory read into GPU registers / dmesg.

Fix validation

not_testable β€” no CIK hardware on the guest to build/boot a patched kernel against. The one-line fix (branch on err immediately after cik_sdma.c:141) is mechanically identical to the request_firmware check two lines above; regression risk ~zero.

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.

(No fix.diff for this item: documented recommendation only - see Fix above.)

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

not_testable β€” no CIK hardware on the audit guest; fix is a two-line error check mirroring the adjacent request_firmware check

fix diff in README.md; no baseline/patched runs possible without the ASIC
per-fix-DF-4359

Confirmed kernel references

Detail

Evidence (decisive lines)

README.md (loop excerpt cik_sdma.c:133-152 + validate body amdgpu_ucode.c:251-260); VERDICT.md (full narrative + guest pciconf check); guest pciconf shows no 0x1002 device

PoC changes

none β€” no PoC seed; static verification only (hardware-gated path)

Verified recommended fix

In cik_sdma_init_microcode, 'if (err) goto out;' immediately after amdgpu_ucode_validate() so a failed validation of any instance aborts the load

Verdict

cik_sdma_init_microcode() assigns the amdgpu_ucode_validate() result to err but never checks it inside the per-instance loop, so only the final instance's result is honored; a corrupt instance-0 SDMA firmware blob that fails the only consistency gate (fw->datasize == hdr->size_bytes) is accepted with err==0 and later consumed unvalidated by cik_sdma_load_microcode (the DF-1553 sink) β€” a distinct missed-error-propagation defect that a DF-1553 fix alone does not close. Runtime PoC impossible on the audit guest (no CIK AMD PCI device), static proof line-accurate; low severity since firmware replacement requires root.