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

cu_info walkers index bitmap[4][4]/ao_cu_bitmap[4][4] with unclamped max_shader_engines/max_sh_per_se taken from gpu_info firmware or VBIOS β†’ heap OOB write + read

Summary

gfx_v9_0_get_cu_info and gfx_v9_0_init_always_on_cu_mask iterate i<max_shader_engines, j<max_sh_per_se and index cu_info->bitmap[i][j]/ao_cu_bitmap[i][j], which are fixed [4][4] arrays inside the heap amdgpu_device. Those bounds arrive as raw u32s from the amdgpufw_<asic>_gpu_info firmware file (validated only by the size-self-consistency check) or as u8s from the VBIOS gfx_info table on VEGA20. A crafted gpu_info blob with gc_num_se=64 turns gfx_v9_0_get_cu_info into a linear heap overwrite of up to ~1 KiB past the 64-byte array - corrupting mutexes, pointers and adjacent amdgpu_device members with GPU-register-derived u32s - plus OOB reads; on Raven the always_on path hits it already at sw_init, before the clamp would run. Generator builds/runs on the guest; runtime injection impossible (virtio-only). Fix: bound the loops to [4][4] AND clamp config after VBIOS read (clamp alone insufficient due to sw_init ordering).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3698 Β· 11 files
FileTypeDescriptionSize
README.md β€” 2.5 KB ↓ raw
VERDICT.md β€” 4.1 KB ↓ raw
fwgen_craft_gpuinfo.c β€” 3.7 KB view raw
build.sh β€” 95 B view raw
run.sh β€” 91 B view raw
build.log β€” 213 B view raw
run.log β€” 417 B view raw
env.txt β€” 221 B view raw
fix.diff β€” 1.9 KB view raw
manifest.json β€” 1.5 KB view raw
verdict.json β€” 3.7 KB view raw

DF-3698 β€” cu_info bitmap[4][4] indexed by unclamped max_shader_engines / max_sh_per_se from firmware β†’ heap OOB write

Where

  • OOB write/read sites: sys/dev/drm/amd/amdgpu/gfx_v9_0.c:4990-5013 (gfx_v9_0_get_cu_info: cu_info->bitmap[i][j], cu_info->ao_cu_bitmap[i][j]) and gfx_v9_0.c:937-958 (gfx_v9_0_init_always_on_cu_mask, same arrays, read at :945, write at :958).
  • Array dimensions: sys/dev/drm/amd/amdgpu/amdgpu_gfx.h:197-198 β€” uint32_t ao_cu_bitmap[4][4]; uint32_t bitmap[4][4]; (inside struct amdgpu_cu_info, embedded as adev->gfx.cu_info in the heap amdgpu_device object).
  • Unclamped bounds source #1 (fw file): amdgpu_device.c:1379-1381 β€” max_shader_engines = le32_to_cpu(gpu_info_fw->gc_num_se) from amdgpufw_<asic>_gpu_info (loaded for vega10/vega12/raven/picasso/raven2, amdgpu_device.c:61-65; amdgpu_ucode_validate here only checks size_bytes == datasize).
  • Unclamped bounds source #2 (VBIOS): gfx_v9_0.c:1422 β†’ amdgpu_atomfirmware.c:349 (gc_num_se u8 ≀ 255 from the atom gfx_info table, VEGA20 path).

Reproduce

build.sh   # cc -O2 -o fwgen_gi fwgen_craft_gpuinfo.c  (ran on DF 6.5 guest, cc 8.3)
run.sh     # ./fwgen_gi -> amdgpufw_raven_gpu_info.evil (96 bytes, gc_num_se=64)

On hardware: install the blob as the gpu_info firmware file and load the driver β€” gc_num_se=64 makes gfx_v9_0_get_cu_info (called from gfx_v9_0_constants_init, gfx_v9_0.c:1971, at hw_init) iterate i = 0..63, writing bitmap[i][j] up to 960 bytes past the end of the 64-byte [4][4] array β€” a linear heap overwrite of everything after adev->gfx.cu_info inside amdgpu_device, with GPU-register-derived u32 values. On Raven, gfx_v9_0_init_always_on_cu_mask (sw_init β†’ gfx_v9_0_rlc_init β†’ init_lbpw) hits the same overflow even earlier. This guest has no AMD GPU (virtio-vga only), so the path cannot run here.

Expected (on hardware)

Kernel heap corruption immediately after adev->gfx.cu_info (mutex pointers, function-pointer-adjacent fields, then adjacent heap objects) β†’ near-certain panic, plausibly exploitable with control of the following heap layout.

Threat model

Attacker controls the gpu_info firmware file (root path replacement / compromised distribution) or the GPU VBIOS. Same threat class as pass-1 findings DF-1176/1177/1178 (firmware-parsing, filed Medium).

Fix

fix.diff β€” bound the loops to the array dimensions in both functions and clamp the config after the VBIOS read in gfx_v9_0_gpu_early_init.

VERDICT.md
↓ download raw

DF-3698 β€” VERDICT

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

The chain, line by line (this tree)

  1. amdgpu_device_ip_early_init (amdgpu_device.c:1490) unconditionally runs amdgpu_device_parse_gpu_info_fw for soc15 asics.
  2. amdgpu_device_parse_gpu_info_fw (amdgpu_device.c:1301-1395) requests amdgpufw_<asic>_gpu_info (vega10/vega12/raven/picasso/raven2 β€” amdgpu_device.c:61-65), "validates" it with amdgpu_ucode_validate which in this tree is only fw->datasize == hdr->size_bytes (amdgpu_ucode.c:251-259), then assigns max_shader_engines = le32(gpu_info_fw->gc_num_se) (:1379) and max_sh_per_se = le32(...gc_num_sh_per_se) (:1381) β€” raw u32s, no clamp. VEGA20 has a second source: gfx_v9_0.c:1422 β†’ amdgpu_atomfirmware_get_gfx_info (amdgpu_atomfirmware.c:349-351, u8 fields from VBIOS, ≀255, still ≫ 4).
  3. gfx_v9_0_get_cu_info (gfx_v9_0.c:4977-5024) loops i < max_shader_engines, j < max_sh_per_se and writes cu_info->bitmap[i][j] = bitmap; (:5000) and cu_info->ao_cu_bitmap[i][j] = ao_bitmap; (:5013). The arrays are fixed [4][4] (amdgpu_gfx.h:197-198). With gc_num_se = 64 the write index runs to bitmap[63][3] β€” 960 bytes past a 64-byte array, inside the heap amdgpu_device. Written values are GPU register reads (CU active bitmaps, small u32s) β€” not fully attacker-chosen, but structurally a linear kernel heap overwrite. It also OOB-reads cu_info->bitmap[i][j] at :945 via gfx_v9_0_init_always_on_cu_mask (:937-958, write at :958), reached from sw_init on Raven (gfx_v9_0_rlc_init β†’ gfx_v9_0_init_lbpw) β€” i.e. before gfx_v9_0_gpu_early_init would ever run, so a clamp placed only there is insufficient (the fix bounds the loops themselves).
  4. Reachability: get_cu_info runs at gfx_v9_0_hw_init β†’ gfx_v9_0_constants_init (gfx_v9_0.c:1971) and again from the AMDGPU_INFO_CUINFO ioctl path (amdgpu_gfx...get_cu_info func table gfx_v9_0.c:1378-1385); init_always_on_cu_mask at sw_init (Raven).

Distinct from DF-1176/1177/1178 (those are RLC/MEC firmware header parsing; this is the gpu_info/VBIOS-derived config feeding fixed-size array loops).

Verification run (guest, virtio-only)

  • Guest: DragonFly 6.5-DEVELOPMENT #0 Thu Jul 2 06:02:54 UTC 2026, x86_64.
  • amdgpu.ko present in /boot/kernel, kldload amdgpu OK β€” the vulnerable code ships in the guest kernel build.
  • pciconf -lv β†’ zero vendor-0x1002 devices; only virtio-vga (chip=0x11111234). amdgpu_pci_probe never matches β†’ the driver never reaches sw_init/parse_gpu_info_fw β†’ runtime-unreachable on this guest; honest not_reproduced, not a refutation: every source link is cited and was re-verified in this tree.
  • Trigger generator builds and runs on the guest (cc 8.3), emitting amdgpufw_raven_gpu_info.evil (96 bytes; header v1.0, gc_num_se=64, gc_num_sh_per_se=4, size_bytes=96=datasize so the tree's validator passes it). Logs: build.log, run.log, env.txt.

Impact ceiling (on hardware)

Heap OOB write with partially controlled length (gc_num_se/gc_num_sh_per_se u32, effectively unbounded iteration β†’ runs until fault) and semi-controlled data (GPU-register-derived u32 bitmaps) over the tail of amdgpu_device β†’ kernel panic at minimum; plausible corruption of mutexes/pointers following adev->gfx.cu_info, i.e. potential privilege escalation given firmware-file control (which already implies root file access, so practically: defense against malicious/corrupt firmware + supply chain).

Fix validation

fix.diff bounds both loops (min(x,4)) and clamps config after the VEGA20 VBIOS read. Not runtime-testable here (no AMD 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-3698

Confirmed kernel references

Detail

Exploit chain

on-hardware chain (not runnable here): craft amdgpufw__gpu_info with gc_num_se=N (self-consistent size_bytes) -> amdgpu_device_parse_gpu_info_fw stores N -> gfx_v9_0_get_cu_info writes bitmap[i][j] for i overwrite of fields following adev->gfx.cu_info (gfx.funcs ptr, mutexes, then adjacent amdgpu_device members) with GPU-register-derived u32s -> corruption/panic, potentially worse with heap layout control.

Evidence (decisive lines)

VERDICT.md (line-by-line trace); fwgen_craft_gpuinfo.c + run.log (blob generated on guest cc 8.3, md5 4097a78162dc4bc1aa5740ffa50b1a54); env.txt (no AMD PCI device; amdgpu.ko loads but binds nothing); fix.diff (loop bounds + config clamp)

PoC changes

n/a (finding + generator authored fresh in this run; no seed PoC existed)

Verified recommended fix

Bound the cu_info walker loops to the [4][4] array dims (min(x,4)) in gfx_v9_0_get_cu_info and gfx_v9_0_init_always_on_cu_mask, and clamp max_shader_engines/max_sh_per_se after the VEGA20 VBIOS read (see fix.diff).

Verdict

Source-certain, runtime-unreachable on this guest. gfx_v9_0_get_cu_info (gfx_v9_0.c:4990-5013) and gfx_v9_0_init_always_on_cu_mask (gfx_v9_0.c:937-958) iterate ibitmap[i][j]/ao_cu_bitmap[i][j], fixed [4][4] (amdgpu_gfx.h:197-198). Those bounds arrive unclamped as raw u32s from the amdgpufw_\_gpu_info firmware file (amdgpu_device.c:1379-1381; the tree's amdgpu_ucode_validate only checks size_bytes==datasize) or as u8s from the VBIOS gfx_info table on VEGA20 (gfx_v9_0.c:1422 -> amdgpu_atomfirmware.c:349). A crafted gpu_info blob (generator in this pack, gc_num_se=64) turns the loops into a linear heap OOB write past adev->gfx.cu_info inside the heap amdgpu_device plus an OOB read, at hw_init (constants_init -> get_cu_info) and, on Raven, already at sw_init (rlc_init -> init_lbpw). Guest ships amdgpu.ko (kldload verified) but has no AMD GPU (0 vendor-0x1002 devices, virtio-vga only) so the probe path never runs: honest not_reproduced, hardware absent. Distinct from known DF-1176/1177/1178 (RLC/MEC fw header parsing).