# DF-4154 — `amdgpu_atombios_init_mc_reg_table`: zero-stride block walk never terminates; unbounded walk + unvalidated offsets read past the VBIOS buffer (amdgpu twin of DF-3741)

File: `sys/dev/drm/amd/amdgpu/amdgpu_atombios.c` (pass 2)

## Reproduced?

**No — hardware-gated** (no AMD GPU on the audit guest; see `env.txt`).
Established at source level.

## Source trace (`amdgpu_atombios_init_mc_reg_table`, lines 1583-1672)

Called from the DPM init paths (si/kv/ci) with a `module_index`; parses
`VRAM_Info` v2_1:

1. **Unvalidated derefs**: `reg_block = (u8*)vram_info + usMemClkPatchTblOffset`
   (`:1607-1609`) and `reg_data = reg_block + 4 + usRegIndexTblSize`
   (`:1610-1613`) — both u16 BIOS values used as pointer offsets from a
   table whose extent is never validated (DF-4104 enabler) → OOB reads
   before the loop even starts. `num_entries` is (correctly) clamped to
   `VBIOS_MC_REGISTER_ARRAY_SIZE` (32) at `:1617-1618`, and the destination
   arrays (`mc_reg_address[32]`, `mc_reg_table_entry[20]`) are respected —
   the write side is fine; it is the *walk* that is unbounded.

2. **Zero-stride infinite loop** (`:1631-1653`):

```c
while ((le32_to_cpu(*(u32 *)reg_data) != END_OF_REG_DATA_BLOCK) &&
       (num_ranges < VBIOS_MAX_AC_TIMING_ENTRIES)) {
        t_mem_id = (*(u32 *)reg_data & MEM_ID_MASK) >> MEM_ID_SHIFT;
        if (module_index == t_mem_id) { ...; num_ranges++; }
        reg_data += le16_to_cpu(reg_block->usRegDataBlkSize);   /* may be 0 */
}
```

The only exits are reading `END_OF_REG_DATA_BLOCK` (0xFFFFFFFF) or
`num_ranges == 20`. `num_ranges` increments **only** when the block's mem id
matches. Crafted `usRegDataBlkSize == 0` + first dword != 0xFFFFFFFF +
`t_mem_id != module_index` ⇒ the loop spins forever on the same word →
kernel hang at DPM init (uninterruptible, IRQ-context-free but boot-path/
bind-path wedge). Identical shape to radeon's `radeon_atom_init_mc_reg_table`
in DF-3741.

3. **Unbounded OOB read walk**: with a small non-zero stride and no END
   marker inside the buffer, `reg_data` marches past the `kmalloc`ed BIOS
   image reading `*(u32*)reg_data` each step (and the inner
   `:1639-1648` loop reads `((u32*)reg_data + j)` for `j < reg_table->last`,
   ≤ 32 extra words per block) until it happens to hit 0xFFFFFFFF in
   adjacent heap or faults. Values land in `reg_table->mc_reg_table_entry[]`
   and are later programmed into MC registers.

## Verdict

INCONCLUSIVE (hardware-gated / not reachable on the audit guest).
Source-certain: (a) deterministic infinite loop on `usRegDataBlkSize == 0`,
(b) unbounded OOB reads of the kernel heap past the BIOS allocation, from a
crafted VBIOS at driver bind/DPM init. Severity Medium (DoS + OOB read,
hardware-gated).

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
