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)
PoC verification
Evidence pack
findings/poc/DF-1163 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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.
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 thecp_tableBO ofcp_table_size = ALIGN(CP_ME_TABLE_SIZE*5*4, 2048) + 64*1024 = 67584bytes = 16896 dwords (gfx_v7_0.c:3300-3301,cikd.h:36 CP_ME_TABLE_SIZE=96).- OOB write (CWE-787):
bo_offset+i >= 16896writes past the cp_table BO. - OOB read (CWE-125):
table_offset+i >= fw->datasize/4reads past the firmware kmalloc blob into adjacent kernel heap (info-leak source). amdgpu_ucode_validate()(amdgpu_ucode.c:256) only checksfw->datasize == hdr->size_bytesand 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_tableduring 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.
Recommended fix
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.diffapplies cleanly (git apply --checkOK; 7 hunks).- Full
amdgpu.komodule build:cd /usr/src/sys/dev/drm/amd && makeβ rc=0,amdgpu.koproduced,gfx_v7_0.o(80360 B) compiled & linked, 0 errors under-Werror. fix_status: not_testablefor 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)
sys/dev/drm/amd/amdgpu/gfx_v7_0.c:3787β function entrysys/dev/drm/amd/amdgpu/gfx_v7_0.c:3821..3845β jt_offset/jt_size read unvalidated per MEsys/dev/drm/amd/amdgpu/gfx_v7_0.c:3847-3852β OOB write (dst) + OOB read (src) loop, bo_offset accumulationsys/dev/drm/amd/amdgpu/gfx_v7_0.c:3300-3301β cp_table_size = 67584 (16896 dwords)sys/dev/drm/amd/amdgpu/cikd.h:36βCP_ME_TABLE_SIZE 96sys/dev/drm/amd/amdgpu/amdgpu_ucode.c:256β ucode_validate only checks datasize
Fix verification
not_testablecompile+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.
No comments yet.