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)
PoC verification
Evidence pack
findings/poc/DF-3598 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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:
- 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. - 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 intoamdgpu_ucode_init_bo'sfw_sizeaccounting (: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.
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
- sys/dev/drm/amd/amdgpu/gfx_v8_0.c:1082 (unchecked validate)
- sys/dev/drm/amd/amdgpu/gfx_v8_0.c:1083-1125 (unguarded consumers)
- sys/dev/drm/amd/amdgpu/gfx_v8_0.c:1019-1021,1039-1041,1060-1062,1140-1142,1160-1163 (guarded contrast)
- sys/dev/drm/amd/amdgpu/amdgpu_ucode.h:79-99 (header size 0x48)
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 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
Confirmed kernel references
Detail
Exploit chain
replace amdgpufw_
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).
No comments yet.