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)
PoC verification
Evidence pack
findings/poc/DF-4359 Β· 4 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 β 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 theout: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_testablenot_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
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.
No comments yet.