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)
PoC verification
Evidence pack
findings/poc/DF-3698 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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]) andgfx_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];(insidestruct amdgpu_cu_info, embedded asadev->gfx.cu_infoin the heapamdgpu_deviceobject). - Unclamped bounds source #1 (fw file):
amdgpu_device.c:1379-1381βmax_shader_engines = le32_to_cpu(gpu_info_fw->gc_num_se)fromamdgpufw_<asic>_gpu_info(loaded for vega10/vega12/raven/picasso/raven2,amdgpu_device.c:61-65;amdgpu_ucode_validatehere only checkssize_bytes == datasize). - Unclamped bounds source #2 (VBIOS):
gfx_v9_0.c:1422βamdgpu_atomfirmware.c:349(gc_num_seu8 β€ 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.
DF-3698 β VERDICT
status: not_reproduced (hardware absent) Β· source-level: certain
The chain, line by line (this tree)
amdgpu_device_ip_early_init(amdgpu_device.c:1490) unconditionally runsamdgpu_device_parse_gpu_info_fwfor soc15 asics.amdgpu_device_parse_gpu_info_fw(amdgpu_device.c:1301-1395) requestsamdgpufw_<asic>_gpu_info(vega10/vega12/raven/picasso/raven2 βamdgpu_device.c:61-65), "validates" it withamdgpu_ucode_validatewhich in this tree is onlyfw->datasize == hdr->size_bytes(amdgpu_ucode.c:251-259), then assignsmax_shader_engines = le32(gpu_info_fw->gc_num_se)(:1379) andmax_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).gfx_v9_0_get_cu_info(gfx_v9_0.c:4977-5024) loopsi < max_shader_engines,j < max_sh_per_seand writescu_info->bitmap[i][j] = bitmap;(:5000) andcu_info->ao_cu_bitmap[i][j] = ao_bitmap;(:5013). The arrays are fixed[4][4](amdgpu_gfx.h:197-198). Withgc_num_se = 64the write index runs tobitmap[63][3]β 960 bytes past a 64-byte array, inside the heapamdgpu_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-readscu_info->bitmap[i][j]at:945viagfx_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. beforegfx_v9_0_gpu_early_initwould ever run, so a clamp placed only there is insufficient (the fix bounds the loops themselves).- Reachability:
get_cu_inforuns atgfx_v9_0_hw_initβgfx_v9_0_constants_init(gfx_v9_0.c:1971) and again from theAMDGPU_INFO_CUINFOioctl path (amdgpu_gfx...get_cu_infofunc tablegfx_v9_0.c:1378-1385);init_always_on_cu_maskat 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.kopresent in/boot/kernel,kldload amdgpuOK β the vulnerable code ships in the guest kernel build.pciconf -lvβ zero vendor-0x1002 devices; only virtio-vga (chip=0x11111234).amdgpu_pci_probenever matches β the driver never reachessw_init/parse_gpu_info_fwβ runtime-unreachable on this guest; honestnot_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), emittingamdgpufw_raven_gpu_info.evil(96 bytes; header v1.0,gc_num_se=64,gc_num_sh_per_se=4,size_bytes=96=datasizeso 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_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:4990
- sys/dev/drm/amd/amdgpu/gfx_v9_0.c:5000
- sys/dev/drm/amd/amdgpu/gfx_v9_0.c:5013
- sys/dev/drm/amd/amdgpu/gfx_v9_0.c:937
- sys/dev/drm/amd/amdgpu/gfx_v9_0.c:945
- sys/dev/drm/amd/amdgpu/gfx_v9_0.c:958
- sys/dev/drm/amd/amdgpu/gfx_v9_0.c:1422
- sys/dev/drm/amd/amdgpu/amdgpu_gfx.h:197
- sys/dev/drm/amd/amdgpu/amdgpu_device.c:1379
- sys/dev/drm/amd/amdgpu/amdgpu_atomfirmware.c:349
Detail
Exploit chain
on-hardware chain (not runnable here): craft amdgpufw_
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 i
No comments yet.