# DF-2202 — amdgpu df_v1_7 OOB array index (source-only confirmation)

## Finding
`df_v1_7_get_hbm_channel_number()` indexes a static 9-element table
`df_v1_7_channel_number[]` (`sys/dev/drm/amd/amdgpu/df_v1_7.c:30`) using the
raw return of `df_v1_7_get_fb_channel_number()` (lines 50-59), which masks HW
register `mmDF_CS_AON0_DramBaseAddress0` down to the `IntLvNumChan` field.
That field is **4-bit** (`MASK 0x000000F0L`, `SHIFT 0x4` —
`df_1_7_sh_mask.h:39,44`) so it yields **0..15**, but the lookup table has
only **9** entries (indices 0..8). Values 9..15 read 1..6 u32 words past the
end of the static const array.

## Status
**Source-only confirmation (HW-gated).** The vulnerable path lives entirely
inside the `amdgpu` DRM module and is only reachable on Vega10 / Vega12
(`CHIP_VEGA10` / `CHIP_VEGA12`) discrete GPU hardware. This QEMU guest has no
such hardware, so the bug cannot be triggered at runtime here. Verification
proceeds by source trace + isolated userspace demonstration of the indexing
defect + kernel-module build of the fix.

## How to reproduce (the indexing defect in isolation)
```sh
cc -Wall -Wextra -o df2202_trigger df2202_trigger.c
./df2202_trigger
```
Expected output (decisive excerpt):

```
table df_v1_7_channel_number[] has 9 entries (indices 0..8)
IntLvNumChan field is 4-bit -> values 0..15
  ...
  field= 9  ->  INDEX  9  OOB  (u32 read at offset 0 word(s) = 0 byte(s) past end)
  ...
  field=15  ->  INDEX 15  OOB  (u32 read at offset 6 word(s) = 24 byte(s) past end)

7 of 16 possible field values land OUT-OF-BOUNDS on the table.
OOB reads land at offset 0..6 u32 words (0..24 bytes) past the end
of the static array, reading whatever 4-byte word lives there.
```

## How to validate the fix
```sh
./build.sh   # builds df2202_trigger (sanity) and applies fix.diff on guest
./run.sh     # runs the trigger; runs the kernel-module build with -Werror
```

The fix is `fix.diff` — adds the exact bounds check that the sibling
`df_v3_6_get_hbm_channel_number()` already has (`df_v3_6.c:67-68`):

```c
fb_channel_number = adev->df_funcs->get_fb_channel_number(adev);
if (fb_channel_number >= ARRAY_SIZE(df_v1_7_channel_number))
    fb_channel_number = 0;
```

## Phase 8 (kernel-module build with -Werror)
On the DragonFly guest (kernel `6.5-DEVELOPMENT #0`, gcc 8.3), the amdgpu
KMOD's natural `CWARNFLAGS` already includes `-Werror` (and `-Wno-pointer-sign`
to suppress an unrelated pre-existing issue in `sys/sys/hash.h`). With the
patch applied:

- `make df_v1_7.o` → **rc=0**, zero warnings/errors.
- `make` (full `amdgpu.ko` link, 3.7 MB) → **rc=0**, zero warnings/errors,
  `df_v1_7.o` is part of the link line.

Full build log: `fix_build.log`.

## Triggerability / threat model (from the finding summary)
The OOB index value comes from a HW register field read at GPU init time on
Vega10/Vega12 (`soc15.c:521-522` wires `df_v1_7_funcs`; `gmc_v9_0.c:812`
calls `get_hbm_channel_number` on every GPU init/re-init when
`amdgpu_atomfirmware_get_vram_width()` returns 0). The register's value is
attacker-influenced through:
1. A malicious ATOM VBIOS ROM on a hotplugged PCIe card;
2. A malicious hypervisor via the SR-IOV VF path;
3. A transient read during early bring-up / reset.

Impact: OOB read 4..24 bytes from kernel `.rodata`/`.data` adjacent to the
table; the OOB-read value is multiplied by `chansize` (64 or 128) at
`gmc_v9_0.c:813` and stored as `adev->gmc.vram_width`, corrupting later
GPUVM/GART/VRAM sizing decisions. Limited info disclosure + possible
memory-miscomputation DoS / follow-on corruption in the GMC init path.

## Files
- `df2202_trigger.c`  — userspace demonstration of the indexing defect (no kernel memory touched)
- `fix.diff`          — git-apply-able fix mirroring `df_v3_6`'s bounds check
- `build.sh`          — exact build command for the trigger
- `run.sh`            — exact run command for the trigger
- `build.log`         — trigger-stub build output
- `run.log`           — trigger-stub run output (the OOB demonstration)
- `fix_build.log`     — full kernel-module build output (Phase 8), rc=0
- `env.txt`           — guest environment (uname, cc version)
- `VERDICT.md`        — full narrative verdict
- `manifest.json`     — machine-readable catalog
