RLC v2_1 save-restore-list offset/size fields parsed without any bounds check β wild-pointer memcpy (OOB kernel heap read / panic)
Summary
For RLC firmware with header version 2.1, gfx_v9_0_init_rlc_ext_microcode computes save_restore_list_{cntl,gpm,srm} = fw->data + raw u32 header offsets and stores the matching u32 sizes with no comparison against fw->datasize. amdgpu_ucode_validate in this tree only checks hdr->size_bytes == fw->datasize, so a self-consistent crafted image passes. On PSP-load devices (Vega10/VEGA12/Raven) amdgpu_ucode_init_single_fw later memcpy's size_bytes from those pointers into a GPU-mapped firmware BO that was sized from the same attacker fields - an OOB read of kernel heap (offset and length each up to 4 GiB) that becomes a page-fault panic or a copy of unrelated kernel heap into GPU-accessible memory. Distinct from DF-1176 (v2_0 reg-list fields). Generator builds/runs on the guest; runtime injection impossible (virtio-only).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3697 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.1 KB | β raw | |
| VERDICT.md | β | 3.9 KB | β raw | |
| fwgen_craft_rlc.c | β | 4.8 KB | view raw | |
| build.sh | β | 92 B | view raw | |
| run.sh | β | 92 B | view raw | |
| build.log | β | 210 B | view raw | |
| run.log | β | 420 B | view raw | |
| env.txt | β | 221 B | view raw | |
| fix.diff | β | 2.3 KB | view raw | |
| manifest.json | β | 1.4 KB | view raw | |
| verdict.json | β | 3.0 KB | view raw |
DF-3697 β RLC v2_1 save-restore-list offset/size fields are never validated β wild-pointer memcpy (OOB kernel heap read)
Where
- Parse:
sys/dev/drm/amd/amdgpu/gfx_v9_0.c:516-535(gfx_v9_0_init_rlc_ext_microcode) - Enable:
sys/dev/drm/amd/amdgpu/gfx_v9_0.c:670-673(is_rlc_v2_1from header version 2.1) and callergfx_v9_0.c:715-716 - Size accounting (PSP):
gfx_v9_0.c:776-797 - Sink:
sys/dev/drm/amd/amdgpu/amdgpu_ucode.c:381-394(amdgpu_ucode_init_single_fw,memcpyfromadev->gfx.rlc.save_restore_list_*), reached viaamdgpu_psp.c:428(amdgpu_ucode_init_bo) on PSP-load devices (Vega10/VEGA12/Raven default).
Reproduce
build.sh # cc -O2 -o fwgen_rlc fwgen_craft_rlc.c (ran on DragonFly 6.5-DEVELOPMENT guest, cc 8.3) run.sh # ./fwgen_rlc -> amdgpufw_raven_rlc.evil (256 bytes)
Deploying the blob on real hardware: place as the RLC firmware file loaded by
the amdgpu driver (amdgpufw_<asic>_rlc, root-owned firmware path) and load
the driver with PSP firmware loading. This guest has no AMD GPU
(virtio-vga only), so the driver probe path cannot run here.
Expected (on hardware)
gfx_v9_0_init_rlc_ext_microcodestoressave_restore_list_cntl = fw->data + 0x10000(fw buffer is 256 bytes β pointer ~64 KiB out of bounds).- On PSP load,
memcpy(kaddr, save_restore_list_cntl, 0x1000)copies 4 KiB of unrelated kernel heap into a GPU-mapped firmware BO; depending on what is mapped atfw->data + 0x10000this is a silent kernel-heap info leak into GPU-visible memory or an immediate page-fault panic. - The destination BO is sized from the same attacker fields, so the write side is in-bounds β the primitive is an OOB read (plus DoS).
Threat model
Attacker must control the RLC firmware image: root replacing the file in the firmware directory, a compromised firmware distribution, or a MITM on an unverified firmware channel. Same threat class as pass-1 findings DF-1176 / DF-1177 / DF-1178 (all firmware-parsing, filed Medium).
Fix
fix.diff β validate every v2_1 save-restore-list offset + size against
fw->datasize and fail init_microcode otherwise (function made fallible).
DF-3697 β VERDICT
status: not_reproduced (hardware absent) Β· source-level: certain
What the code does
gfx_v9_0_init_microcode(gfx_v9_0.c:663-668) requests the RLC firmware and reads the header. Header version 2.1 setsadev->gfx.rlc.is_rlc_v2_1(:670-673).- For v2_1 images,
gfx_v9_0_init_rlc_ext_microcode(:516-535, called at:715-716) computes three pointers straight from unvalidated u32 header fields:
c
adev->gfx.rlc.save_restore_list_cntl =
(u8 *)rlc_hdr + le32_to_cpu(rlc_hdr->save_restore_list_cntl_offset_bytes);
adev->gfx.rlc.save_restore_list_gpm = (u8 *)rlc_hdr + ..._gpm_offset_bytes;
adev->gfx.rlc.save_restore_list_srm = (u8 *)rlc_hdr + ..._srm_offset_bytes;
with *_size_bytes likewise unchecked. Nothing in this tree constrains
them: amdgpu_ucode_validate (amdgpu_ucode.c:251-259) only checks
fw->datasize == hdr->size_bytes β a self-consistency check that a crafted
image trivially satisfies (the generator sets size_bytes = 256 = filesize).
3. On PSP-load devices the three sub-images are registered
(gfx_v9_0.c:776-797) and later copied by
amdgpu_ucode_init_single_fw (amdgpu_ucode.c:381-394):
c
ucode->ucode_size = adev->gfx.rlc.save_restore_list_cntl_size_bytes;
memcpy(ucode->kaddr, adev->gfx.rlc.save_restore_list_cntl, ucode->ucode_size);
Destination BO is allocated using the same attacker fields
(amdgpu_ucode_init_bo, amdgpu_psp.c:428), so dst is sized to match:
the bug is an out-of-bounds read of size_bytes from
fw->data + offset (offset/size up to 4 GiB each) β wild-pointer read β
page-fault panic, or a copy of unrelated kernel heap into a GPU-mapped BO.
This is distinct from DF-1176, which covers the v2_0 reg-list fields
(reg_list_format_* kmalloc + copy loops at gfx_v9_0.c:695-713); this
finding is the v2_1 extension block (different fields, different function,
different sink β the ucode-loader memcpy).
Verification run (guest, virtio-only)
- Guest: DragonFly 6.5-DEVELOPMENT
#0 Thu Jul 2 06:02:54 UTC 2026, x86_64. amdgpu.kois present in/boot/kernelandkldload amdgpusucceeds ([drm] amdgpu kernel modesetting enabled.), proving the vulnerable code ships compiled in this kernel build.- PCI scan shows only virtio-vga (
vgapci0@pci0:0:2.0 chip=0x11111234) β no vendor 0x1002 device, soamdgpu_pci_probenever matches,gfx_v9_0_sw_init/init_microcodenever execute β the parse path is unreachable at runtime on this guest. - The trigger generator builds cleanly with the guest toolchain (
cc 8.3) and producesamdgpufw_raven_rlc.evil(256 bytes) whose bytes drive the bug on real hardware: header 2.1,srlc_offset = 0x10000,srlc_size = 0x1000. Build+run logs:build.log,run.log,env.txt.
Honest classification
not_reproduced β not because the path was traced dead (it is live for every
v2_1 RLC image on PSP-load Vega10/12/Raven hardware), but because the audit
guest has no AMD GPU to bind. Source-level certainty: every link in the chain
is cited above and re-checked line-by-line in this tree.
Impact ceiling on hardware: kernel memory disclosure into GPU-accessible memory (contents of whatever follows the firmware allocation) or DoS panic. Not a write primitive.
Fix validation
fix.diff adds offset + size <= fw->datasize checks for all three
save-restore-list blobs and makes gfx_v9_0_init_rlc_ext_microcode fail
init_microcode on violation. Not runtime-testable here (no hardware);
fix_status: not_tested, baseline not reproduced for the same reason.
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 authored post-verification against the read-only sys/ tree; cannot be runtime-validated on this guest (no AMD hardware to bind the driver).
fix.diff in this pack; not applied to sys/.
Confirmed kernel references
- sys/dev/drm/amd/amdgpu/gfx_v9_0.c:516
- sys/dev/drm/amd/amdgpu/gfx_v9_0.c:524
- sys/dev/drm/amd/amdgpu/gfx_v9_0.c:528
- sys/dev/drm/amd/amdgpu/gfx_v9_0.c:532
- sys/dev/drm/amd/amdgpu/gfx_v9_0.c:715
- sys/dev/drm/amd/amdgpu/gfx_v9_0.c:776
- sys/dev/drm/amd/amdgpu/amdgpu_ucode.c:381
- sys/dev/drm/amd/amdgpu/amdgpu_ucode.c:251
- sys/dev/drm/amd/amdgpu/amdgpu_psp.c:428
Detail
Evidence (decisive lines)
VERDICT.md (full path trace with path:line); fwgen_craft_rlc.c + run.log (blob built and generated on guest cc 8.3, md5 c041dbf69d66c0007e2844fe18385c4a); env.txt (no vendor 0x1002 device, amdgpu.ko loads but binds nothing); fix.diff (offset+size<=datasize validation)
PoC changes
n/a (finding + generator authored fresh in this run; no seed PoC existed)
Verified recommended fix
Validate save_restore_list_{cntl,gpm,srm} offset+size against rlc_fw->datasize in gfx_v9_0_init_rlc_ext_microcode and fail init_microcode on violation (see fix.diff).
Verdict
Source-certain, runtime-unreachable on this guest. gfx_v9_0_init_rlc_ext_microcode (gfx_v9_0.c:516-535) stores save_restore_list_{cntl,gpm,srm} = fw->data + raw u32 header offsets with sizes likewise unvalidated; amdgpu_ucode_validate in this tree only checks size_bytes==datasize, so a crafted self-consistent v2_1 RLC image (generator in this pack) yields a memcpy (amdgpu_ucode.c:381-394, via amdgpu_psp.c:428) of up to 4GiB from up to 4GiB past the firmware allocation into a GPU-mapped firmware BO sized from the same fields: OOB kernel-heap read / wild-pointer panic. Distinct from DF-1176 (v2_0 reg-list fields). The DragonFly 6.5-DEVELOPMENT guest ships amdgpu.ko (kldload verified) but has no AMD GPU (virtio-vga only), so the probe path never runs: honest not_reproduced, hardware absent.
No comments yet.