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

gfx_v8_0_init_microcode ignores amdgpu_ucode_validate() result for the RLC firmware (asymmetric size gate)

Summary

err = amdgpu_ucode_validate(adev->gfx.rlc_fw) is never checked, unlike pfp/me/ce/mec/mec2 (all 'if (err) goto out;'). A wrong-sized RLC image falls straight through to rlc_firmware_header_v2_0 parsing and the register-list kmalloc/copy loops; a file shorter than the 72-byte header makes the field reads themselves OOB, and the skipped datasize==size_bytes gate feeds garbage sizes/offsets into the DF-1133 heap-OOW loops and fw_size accounting. Same size-gate-symmetry class as DF-3573/DF-3591. Source assertion runnable anywhere in the tree (run.sh prints '1082 UNGUARDED' vs five GUARDED sites). Composes with DF-1133 (High) and DF-1946/1947 - this missing check is the local gate those findings assume.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3598 Β· 7 files
FileTypeDescriptionSize
run.sh β€” 89 B view raw
run.log β€” 441 B view raw
README.md β€” 2.2 KB ↓ raw
VERDICT.md β€” 2.6 KB ↓ raw
fix.diff β€” 428 B view raw
manifest.json β€” 779 B view raw
verdict.json β€” 2.4 KB view raw

DF-3598 β€” gfx_v8_0_init_microcode ignores amdgpu_ucode_validate() result for the RLC firmware

What

gfx_v8_0.c:1082 assigns err = amdgpu_ucode_validate(adev->gfx.rlc_fw); and never checks it β€” unlike every other firmware in the same function:

fw validate checked at
pfp gfx_v8_0.c:1019 :1020 if (err) goto out;
me gfx_v8_0.c:1039 :1040
ce gfx_v8_0.c:1060 :1061
rlc gfx_v8_0.c:1082 (none)
mec gfx_v8_0.c:1140 :1141
mec2 gfx_v8_0.c:1161 :1162

With a wrong-sized RLC image the function falls through and immediately parses rlc_firmware_header_v2_0 fields at :1083-1104 (ucode_version, save_and_restore_offset, reg_list_format_size_bytes, reg_list_format_array_offset_bytes, …) and then performs the kmalloc+copy loops at :1106-1125 on completely unvalidated data.

Two consequences:

  1. A firmware file smaller than sizeof(struct rlc_firmware_header_v2_0) (0x50 bytes, amdgpu_ucode.h:79-99) makes the header field reads themselves out-of-bounds reads of the firmware buffer.
  2. The (weak but only) consistency gate β€” fw->datasize == hdr->size_bytes β€” being skipped means garbage sizes/offsets flow straight into the DF-1133 register-list copy loops and into amdgpu_ucode_init_bo's fw_size accounting (:1197-1209).

This mirrors the DF-3573/DF-3591 "size-gate-symmetry" lesson from the si_dpm/cik passes: a size gate that is asymmetrically enforced lets a bogus firmware through to bounded-wrong consumers.

Reachability / threat model

Driver load on any VI asic with a corrupted/mismatched amdgpufw_<asic>_rlc (root-replaceable firmware module) β€” no other precondition. Composes with DF-1133 (High) and DF-1946/DF-1947 (amdgpu_ucode.c) β€” this missing check is the local gate those findings assume.

Reproduce (source assertion)

./run.sh     # prints the five validate sites and shows the rlc one has no guard

Kernel-side execution requires AMD hardware (audit guest is virtio-only) β€” verdict inconclusive at runtime, certain at source level.

Fix

fix.diff: add if (err) goto out; after gfx_v8_0.c:1082, symmetric with the other five loaders.

VERDICT.md
↓ download raw

DF-3598 β€” VERDICT

status: inconclusive (kernel run) / source certain reproduced: 0

Proof

awk-verified call sites in gfx_v8_0_init_microcode (sys/dev/drm/amdgpu/gfx_v8_0.c):

1019  GUARDED    amdgpu_ucode_validate(pfp_fw)   -> :1020 if (err) goto out
1039  GUARDED    amdgpu_ucode_validate(me_fw)    -> :1040
1060  GUARDED    amdgpu_ucode_validate(ce_fw)    -> :1061
1082  UNGUARDED  amdgpu_ucode_validate(rlc_fw)   -> next executable statement is
                                                    rlc_hdr = ... rlc_fw->data (:1083)
1140  GUARDED    amdgpu_ucode_validate(mec_fw)   -> :1141
1161  GUARDED    amdgpu_ucode_validate(mec2_fw)  -> :1162

err is not consumed between :1082 and the next assignment (err = request_firmware(... mec ...) :1129), so the RLC validation result is silently discarded and a wrong-sized RLC firmware proceeds to header parsing (:1083-1104) and the register-list kmalloc/copy (:1106-1125).

Blast radius if the RLC file is truncated below sizeof(struct rlc_firmware_header_v2_0) = 72 bytes (20 header dwords + 18 v2.0 dwords, amdgpu_ucode.h:79-99): the field reads at :1083-1104 are themselves OOB reads of the firmware buffer; the garbage sizes/offsets then feed DF-1133's copy loops (heap OOB) and adev->firmware.fw_size (:1197-1209). With a merely mismatched (but structurally large) file, the skipped datasize == size_bytes gate lets inconsistent lengths through.

Why not a kernel run

Audit guest is virtio-only: no AMD PCI device, no drm/amdgpu module (kldstat -v | grep -c drm = 0), /boot/modules absent. Loading a crafted amdgpufw_*_rlc requires the driver to bind real VI hardware. Hence inconclusive at runtime; the source defect is unambiguous.

Fix validation

fix.diff adds the two-line symmetric guard after :1082. Single-fix kernel build not possible on this guest (module absent) β€” fix_status: not_testable; diff line-matched against the current tree.

kernel_refs

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 adds the missing guard, line-matched to the current tree; kernel build/run not possible on virtio-only guest (amdgpu module absent).

findings/poc/DF-3598/fix.diff
↓ fix.diffper-fix-DF-3598

Confirmed kernel references

Detail

Exploit chain

replace amdgpufw__rlc with truncated/corrupt image -> init_microcode proceeds despite validate failure -> OOB header reads / garbage list sizes -> composes with DF-1133 heap OOB write. Not executed (no AMD HW).

Evidence (decisive lines)

run.log (awk proof: 1082 UNGUARDED, others GUARDED); VERDICT.md; fix.diff (2-line symmetric guard)

PoC changes

authored fresh (pass 2); no seed existed

Verified recommended fix

Add 'if (err) goto out;' after gfx_v8_0.c:1082, symmetric with the pfp/me/ce/mec/mec2 loaders.

Verdict

gfx_v8_0.c:1082 assigns err = amdgpu_ucode_validate(adev->gfx.rlc_fw) and never checks it (next err assignment is request_firmware at :1129), while pfp/me/ce/mec/mec2 all have 'if (err) goto out' (:1020, :1040, :1061, :1141, :1162). A wrong-sized RLC firmware therefore proceeds to rlc_firmware_header_v2_0 field parsing (:1083-1104) and the register-list kmalloc/copy loops (:1106-1125); a file shorter than the 72-byte header makes those reads OOB, and the skipped datasize==size_bytes gate lets garbage sizes/offsets reach the DF-1133 heap-OOB loops and fw_size accounting. Kernel run impossible on the virtio-only guest (no AMD device, no amdgpu module); source-level proof via run.sh output (1082 UNGUARDED vs 5x GUARDED).