# DF-4862 — amdgpu_cgs CP_MEC `image_size = jt_offset << 2` unbounded (SMU-side OOB DMA read)

## What

`amdgpu_cgs_get_firmware_info()` non-SMU branch
(`sys/dev/drm/amd/amdgpu/amdgpu_cgs.c:234-235`):

```c
if (CGS_UCODE_ID_CP_MEC == type)
        info->image_size = le32_to_cpu(header->jt_offset) << 2;
```

`jt_offset` is a raw dword from the MEC gfx firmware file header
(`gfx_firmware_header_v1_0.jt_offset`, `amdgpu_ucode.h:64`). It is never
bounded — not against `ucode_size_bytes`, not against the file, not against
the staged copy window. `<<2` wraps mod 2^32, so any value 0..0xFFFFFFFC can
be produced. This is the same unchecked-header-window shape as DF-4774
(which bounded the SMU branch); the CP branch was missed.

## Consumer chain (why it matters)

- `smu7_populate_single_firmware_entry()` `smu7_smumgr.c:305-327`:
  `entry->data_size_byte = info.image_size` (:327)
- `smu7_request_smu_load_fw()` `smu7_smumgr.c:405` populates the CP_MEC TOC
  entry, `:424` `memcpy_toio`s the TOC to SMU-visible DRAM, `:431` sends
  `PPSMC_MSG_LoadUcodes` — after which the **SMU DMA engine** copies
  `data_size_byte` bytes from `entry.image_addr`, the MEC1 window inside
  `adev->firmware.fw_buf` staged by `amdgpu_ucode_init_bo()`
  (`amdgpu_ucode.c:449-485`) as `ALIGN(ucode_size_bytes,PAGE)` (+ JT pages).
- With a crafted MEC header the SMU reads far beyond the staged window:
  adjacent GTT pages are disclosed into SMC SRAM (device-side), and the
  unmapped range beyond triggers a GPU page fault / SMU hang (driver-load
  failure → repeated GPU resets → system-level DoS).
- The only validation anywhere on the path is `amdgpu_ucode_validate()`'s
  `datasize == size_bytes` (`amdgpu_ucode.c:251-259`, DF-1946).

Distinct from DF-1947 (staging memcpy OOB in `amdgpu_ucode.c`): scenario [2]
of the harness has **zero kernel-side OOB reads** — the file is entirely
self-consistent — yet the SMU-side window is 8x oversized. The kernel never
notices; the GPU-side DMA does the damage.

## Build / run / expected

```
sh build.sh    # cc -O0 -g -Wall both binaries (baseline + patched twin)
sh run.sh      # runs baseline then patched twin
```

Expected (baseline):

- `[1]` legit header (jt_offset=0x300 dwords): `data_size_byte=0xc00`,
  SMU DMA fully in-bounds — no false positive.
- `[2]` crafted header (jt_offset=0x4000 dwords, everything else sane,
  validate PASSes): `data_size_byte=0x10000` vs staged window `0x2000` →
  `RESULT: SMU-SIDE OOB DMA READ CONFIRMED: 57344 bytes beyond the staged
  fw_buf window + unmapped-page fault class`, with
  `kernel-side OOB reads during init_bo/patch_jt: 0 bytes`.
- `[3]` jt_offset=0x3F000000: child killed by SIGSEGV — the kernel-side
  `patch_jt` wild source (DF-1947 overlap) faults first at driver load;
  shows the extreme end of the same unvalidated field.
- `[4]` (also DF-4863): `ucode[AMDGPU_UCODE_ID_MAXIMUM].fw` aliases
  `firmware.fw_buf` — `ALIAS CONFIRMED`, garbage propagated to caller.

Expected (patched twin, `fix.diff` at the construction): `[1]` still loads
identically; `[2]` `populate CP_MEC entry: -22 (rejected at the cgs
construction — FIX VALIDATED)`; `[4]` `-22` one-past-end index rejected.

## Harness methodology

No AMD GPU exists on the audit guest, so (per DF-4774 / DF-4665 / DF-3683 /
DF-2096) the harness copies the cited function bodies 1:1 —
`fw_type_convert`, `amdgpu_get_firmware_version`, the non-SMU branch of
`amdgpu_cgs_get_firmware_info`, `amdgpu_ucode_validate`, the staging math of
`amdgpu_ucode_init_single_fw`/`amdgpu_ucode_patch_jt`/`amdgpu_ucode_init_bo`,
and `smu7_populate_single_firmware_entry` — with 1:1 struct layouts
(`_Static_assert`ed against the real field offsets) and a mock GTT/SMU-DMA
engine whose mapping model (staged BO + adjacent mapped page + unmapped
guard) mirrors the real GPU address space. `[VERBATIM]` markers in the
source cite each block's kernel lines.

Deterministic: 3/3 runs identical (`run.log`, `run.2.log`, `run.3.log`).
