# DF-3597 — gfx_v8_0_parse_ind_reg_list: trailing-entry heap OOB read/write + BUG_ON panic (RLC save/restore list walk)

## What

`gfx_v8_0_parse_ind_reg_list()` (sys/dev/drm/amd/amdgpu/gfx_v8_0.c:3974-4022)
walks the RLC "indirect register list" copied out of the RLC v2.0 firmware
header in 3-dword entries. Inside the loop it does `ind_offset += 2` and then
dereferences `register_list_format[ind_offset]` (read at :4008/:4013, **write**
at :4020) **without re-checking `ind_offset < list_size`**. If the firmware list
ends on a truncated entry (last word != 0xFFFFFFFF at index `list_size-1` or
`list_size-2`), the function:

* **reads** 4-8 bytes past the end of the `kmalloc(reg_list_format_size_bytes)`
  heap buffer (gfx_v8_0.c:4033-4038 — a *fresh* copy, so this is OOB even when
  DF-1133's copy bounds are fixed), and
* **writes** a small integer (the dedup index, 0..7) 4-8 bytes past the end of
  that heap allocation (gfx_v8_0.c:4020), corrupting the adjacent slab object,
  and
* additionally `BUG_ON()`s (kernel panic) when the firmware declares more than
  7 unique indirect indices (:4017) or more than 9 list entries (:3993) — the
  checks fire *after* the array writes they nominally guard.

Distinct from pass-1 DF-1133 (which covers the `kmalloc(a+b)` wrap and the
unvalidated `reg_list_format_array_offset_bytes` in `gfx_v8_0_init_microcode`);
this finding is the *parser's own* bounds defect on a correctly-sized local
copy, reachable even with DF-1133 fixed.

## Reachability (source-proven)

```
gfx_v8_0_hw_init                    gfx_v8_0.c:4864
  -> gfx_v8_0_rlc_resume            gfx_v8_0.c:4872 -> :4176
    -> gfx_v8_0_init_pg             gfx_v8_0.c:4180 -> :4127
         (CHIP_CARRIZO|CHIP_STONEY|CHIP_POLARIS11|CHIP_POLARIS12|CHIP_VEGAM)
       -> gfx_v8_0_init_save_restore_list   :4132/:4141 -> :4024
           kmalloc(reg_list_format_size_bytes) :4033
           memcpy(list copy)                   :4037
           -> gfx_v8_0_parse_ind_reg_list     :4040 -> :3974
```
Driver load / resume / GPU reset on any CZ, Stoney, Polaris11/12, VegaM APU
parses the firmware-supplied list through this function.

## Threat model

Malicious or malformed RLC firmware image (same model as DF-1133/DF-1134: the
firmware blob is `amdgpufw_<asic>_rlc`, loaded as a kernel firmware module —
root-replaceable, distro/mirror-supplied). The OOB write is a classic
small-value / attacker-chosen-offset heap overflow: the allocation size
(`reg_list_format_size_bytes`) selects the slab, the truncation point selects
whether +4 or +8 bytes past the end are clobbered.

## Reproduce (logic replica — kernel path needs AMD HW)

```
./build.sh   # cc -O0 -Wall -o poc_dfr3597 poc_dfr3597.c  (guest has cc 8.3)
./run.sh
```
Expected (observed on the audit guest, DragonFly 6.5-DEVELOPMENT, cc 8.3):

```
mode=oob: buffer=18 live ints (72 bytes = reg_list_format_size_bytes)
last live word [17] = 0xb0000 != 0xFFFFFFFF -> parser does ind_offset += 2
then READS  register_list_format[19]  (4-8 bytes past end)
then WRITES register_list_format[19] = indices  (heap OOB WRITE)
RESULT: canary at live-index 19 (byte +4 past allocation) CLOBBERED to 0x1
HEAP OOB WRITE CONFIRMED (value written = small index int, offset 4 bytes past end)
...
mode=bugon: list_size=38 elems, 8 unique indirect indices
  [BUG_ON fired: kernel would PANIC here]
indices_count=8 bugon_fired=1 -> PANIC (BUG_ON) proven
```

The harness is a verbatim copy of the kernel parser body; only `BUG_ON` is
redefined to report instead of halting. Canary placement emulates the adjacent
slab object.

## Kernel-side verification status

The audit QEMU guest is virtio-only (no AMD GPU; `kldstat -v | grep -c drm`
= 0, no /boot/modules, `pciconf -l` shows no 0x1002 device) so the in-kernel
path could not be executed: verdict `inconclusive` for the kernel run, while
the algorithm-level OOB is proven deterministically by the replica and by
line-by-line source analysis above.

## Fix

`fix.diff` in this directory: bound-check `ind_offset + 2 < list_size` before
the dereference (dropping truncated entries with an error), and convert the
post-write `BUG_ON`s into pre-write `WARN_ON` guards that return.
