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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3697 Β· 11 files
FileTypeDescriptionSize
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

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_microcode stores save_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 at fw->data + 0x10000 this 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).

VERDICT.md
↓ download raw

DF-3697 β€” VERDICT

status: not_reproduced (hardware absent) Β· source-level: certain

What the code does

  1. gfx_v9_0_init_microcode (gfx_v9_0.c:663-668) requests the RLC firmware and reads the header. Header version 2.1 sets adev->gfx.rlc.is_rlc_v2_1 (:670-673).
  2. 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.ko is present in /boot/kernel and kldload amdgpu succeeds ([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, so amdgpu_pci_probe never matches, gfx_v9_0_sw_init/init_microcode never execute β†’ the parse path is unreachable at runtime on this guest.
  • The trigger generator builds cleanly with the guest toolchain (cc 8.3) and produces amdgpufw_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_testable
baseline no→ patch + rebuild →patched clean

fix.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/.
↓ fix.diffper-fix-DF-3697

Confirmed kernel references

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.