# 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`.

## 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.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)
- `sys/dev/drm/amd/amdgpu/gfx_v7_0.c:3787` — function entry
- `sys/dev/drm/amd/amdgpu/gfx_v7_0.c:3821..3845` — jt_offset/jt_size read unvalidated per ME
- `sys/dev/drm/amd/amdgpu/gfx_v7_0.c:3847-3852` — OOB write (dst) + OOB read (src) loop, bo_offset accumulation
- `sys/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 96`
- `sys/dev/drm/amd/amdgpu/amdgpu_ucode.c:256` — ucode_validate only checks datasize
