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

gfx_v7_0_init_cp_pg_table heap OOB write+read via untrusted firmware jt_offset/jt_size

Summary

gfx_v7_0_init_cp_pg_table at gfx_v7_0.c:3788-3854: for each CE/PFP/ME/MEC/MEC2 firmware, reads jt_offset/jt_size from firmware header without validation. Loop at :3847-3850: dst_ptr[bo_offset+i]=fw_data[table_offset+i] writes into cp_table_obj BO (67584 bytes=16896 u32s). No bound on table_size vs ucode_size or cp_table_size; no bound on table_offset vs fw->datasize. Crafted firmware with large jt_size overflows cp_table_obj into adjacent kernel heap with attacker-controlled dwords. Same class as DF-1134 (gfx_v8_0 cz_init_cp_jump_table). Attacker: malicious firmware via root/VFIO/QEMU. Fix: validate jt_offset+jt_size <= ucode_size_bytes/4 and bo_offset+table_size <= cp_table_size/4.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1163 Β· 11 files
FileTypeDescriptionSize
harness.c trigger-source userspace replica of gfx_v7_0_init_cp_pg_table loop; counts OOB write/read iters for crafted jt_offset/jt_size 5.1 KB view raw
fix.diff suggested-fix git-apply-able: capture fw_dwords per ME, reject jt bounds vs fw blob and cp_table before copy 2.7 KB view raw
build.sh build-script cc -O2 -Wall -o harness harness.c 407 B view raw
run.sh run-script ./harness 244 B view raw
build.log build-log amdgpu.ko module build with fix applied (gfx_v7_0.o, -Werror, rc=0) 1.3 KB view raw
run.log run-log decisive harness run: 4300 OOB-write + 18072 OOB-read iters, fix -> 0 1.2 KB view raw
env.txt environment guest uname/cc, no AMD GPU, amdgpu not in GENERIC 1.3 KB view raw
VERDICT.md verdict full narrative: mechanism, harness, threat model, uid0 assessment, fix validation 4.5 KB ↓ raw
README.md readme how to reproduce + status 1.2 KB ↓ raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
README.md readme how to reproduce + status
↓ download raw

DF-1163 β€” PoC evidence pack

Heap OOB write+read in gfx_v7_0_init_cp_pg_table (amdgpu sys/dev/drm/amd/amdgpu/gfx_v7_0.c:3787-3852).

For each of up to 5 ME firmware blobs, jt_offset/jt_size are read straight from the firmware header with no validation and copied into the 16896-dword cp_table BO (dst_ptr[bo_offset+i] = fw_data[table_offset+i]), with bo_offset += jt_size accumulating across all MEs. A crafted firmware header overflows the cp_table BO (OOB write) and/or reads past the firmware blob (OOB read / heap info leak). gfx_v7 twin of DF-1134.

Status

Source-confirmed + harness. Latent at runtime on this guest: amdgpu is NOT in X86_64_GENERIC, and the guest has no AMD GPU.

Reproduce

./build.sh          # cc -O2 -Wall -o harness harness.c   (unprivileged maxx)
./run.sh            # prints OOB write/read iterations + fix check

Expected (harness): 4300 OOB-write iters, 18072 OOB-read iters; fix β†’ 0.

Fix

fix.diff rejects (per ME) jt_size/jt_offset that read past the firmware blob or write past the cp_table. Validated: applies + compiles clean (full amdgpu.ko build, gfx_v7_0.o, -Werror). See VERDICT.md.

VERDICT.md verdict full narrative: mechanism, harness, threat model, uid0 assessment, fix validation
↓ download raw

DF-1163 β€” Heap OOB write+read in gfx_v7_0_init_cp_pg_table (amdgpu gfx_v7_0.c)

Verdict

CONFIRMED via source-trace + userspace harness. On-guest: latent (HW-gated β€” no AMD CIK GPU; amdgpu not in GENERIC). The bug is a genuine unbounded-write-into-fixed-BO + unbounded-read-from-firmware-blob, confirmed by source trace and a harness that reproduces the exact loop math. It is the gfx_v7 twin of DF-1134 (gfx_v8 cz_init_cp_jump_table). amdgpu is optional amdgpu drm, NOT in X86_64_GENERIC, and the guest has no AMD GPU, so the path is runtime-unreachable (valid hard blocker). No exploit.c: not a userspace-reachable primitive.

Mechanism (trigger β†’ primitive β†’ effect)

gfx_v7_0_init_cp_pg_table() (sys/dev/drm/amd/amdgpu/gfx_v7_0.c:3787-3853) iterates over up to 5 (4 normally; 5 on KAVERI) ME firmware blobs (CE/PFP/ME/MEC/MEC2). For each it reads

   table_offset = le32_to_cpu(hdr->jt_offset);   // u32, UNVALIDATED   gfx_v7_0.c:3821..
   table_size   = le32_to_cpu(hdr->jt_size);     // u32, UNVALIDATED
   fw_data = fw->data + ucode_array_offset_bytes;                 // UNCHECKED

straight from the firmware header, then runs

   gfx_v7_0.c:3847  for (i = 0; i < table_size; i++)
   gfx_v7_0.c:3848      dst_ptr[bo_offset + i] = le32(fw_data[table_offset + i]);
   gfx_v7_0.c:3852  bo_offset += table_size;        // accumulates across all MEs
  • dst_ptr = adev->gfx.rlc.cp_table_ptr, the mapping of the cp_table BO of cp_table_size = ALIGN(CP_ME_TABLE_SIZE*5*4, 2048) + 64*1024 = 67584 bytes = 16896 dwords (gfx_v7_0.c:3300-3301, cikd.h:36 CP_ME_TABLE_SIZE=96).
  • OOB write (CWE-787): bo_offset+i >= 16896 writes past the cp_table BO.
  • OOB read (CWE-125): table_offset+i >= fw->datasize/4 reads past the firmware kmalloc blob into adjacent kernel heap (info-leak source).
  • amdgpu_ucode_validate() (amdgpu_ucode.c:256) only checks fw->datasize == hdr->size_bytes and validates neither jt_offset, jt_size, nor the ucode-array offset.

Evidence (harness)

harness.c models 4 ME blobs with attacker-chosen jt_offset/jt_size and a finite firmware blob. Result: cumulative bo_offset = 21196 > 16896 capacity; 4300 OOB-write iterations (dst past cp_table) and 18072 OOB-read iterations (src past fw blob). With the fix (reject jt bounds vs fw blob and cp_table), 0 OOB remain. (Full output in run.log.)

Threat model / reachability

  • Attacker: malicious CE/PFP/ME/MEC/MEC2 firmware headers loaded by amdgpu (root/VFIO/QEMU presenting a crafted GPU). Reached at gfx_v7_0_init_cp_pg_table during CP RLC init.
  • On this guest: NOT reachable. amdgpu is not in GENERIC; only QEMU std VGA. Realistic ceiling on physical AMD CIK HW: panic (VRAM OOB fault / corrupted GPU state) and/or heap info leak.

Exploit chain

None β€” valid hard blocker (HW-gated; amdgpu absent from the guest). No unprivileged-guest syscall injects GPU firmware headers.

PoC changes

Authored from scratch. Deliverables: harness.c, fix.diff, build.sh, run.sh, VERDICT.md, manifest.json, env.txt, build.log, run.log.

fix.diff captures fw_dwords = fw->datasize / 4 per ME branch, computes cp_dwords = cp_table_size / 4 once, and before the copy loop rejects (with a dev_warn + continue) any jt_size/jt_offset that would read past the firmware blob or write past the cp_table BO. This matches the finding proposal ("validate jt_offset+jt_size <= ucode_size_bytes/4 and bo_offset+table_size <= cp_table_size/4") and mirrors DF-1134's fix shape.

Fix validation (Phase 8)

  • fix.diff applies cleanly (git apply --check OK; 7 hunks).
  • Full amdgpu.ko module build: cd /usr/src/sys/dev/drm/amd && make β†’ rc=0, amdgpu.ko produced, gfx_v7_0.o (80360 B) compiled & linked, 0 errors under -Werror.
  • fix_status: not_testable for runtime: amdgpu not in GENERIC, no AMD GPU. Validated at apply + compile level: with the guard, every ME whose jt bounds would OOB the fw blob or cp_table is skipped.

Kernel references (confirmed)

Fix verification

not_testable

compile+harness validated

see evidence pack

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source-confirmed+harness. gfx_v7_0_init_cp_pg_table jt_offset/jt_size unvalidated -> 4300 OOB write + 18072 OOB read. amdgpu not in GENERIC.