# DF-1176 — `gfx_v9_0_init_microcode` u32 integer overflow + OOB

## Finding

`gfx_v9_0_init_microcode()` at `sys/dev/drm/amd/amdgpu/gfx_v9_0.c:691-713`
parses an attacker-controlled RLC firmware header without validating internal
sizes or offsets, producing two distinct vulnerabilities.

### Bug 1 — Integer overflow in `kmalloc` size (lines 691-697)

```c
adev->gfx.rlc.reg_list_format_size_bytes =
        le32_to_cpu(rlc_hdr->reg_list_format_size_bytes);  /* :691 u32 */
adev->gfx.rlc.reg_list_size_bytes =
        le32_to_cpu(rlc_hdr->reg_list_size_bytes);         /* :693 u32 */
adev->gfx.rlc.register_list_format =
        kmalloc(adev->gfx.rlc.reg_list_format_size_bytes +
                adev->gfx.rlc.reg_list_size_bytes, M_DRM, GFP_KERNEL);  /* :696 */
```

Both sizes are `uint32_t` (`amdgpu_gfx.h:76-77`, populated from
`amdgpu_ucode.h:91,93`). Their sum is computed in 32-bit arithmetic. A
crafted firmware header with `reg_list_format_size_bytes = 0x80000010` and
`reg_list_size_bytes = 0x80000010` produces `sum = 0x20` (32 bytes), so
`kmalloc(32)` is called. The subsequent copy loop at lines 705-706 then runs
`size_a >> 2 = 0x20000004` iterations, writing ~2 GB into a 32-byte
allocation → **structured heap overflow / slab corruption**.

### Bug 2 — Unvalidated firmware offsets (lines 703-713)

```c
tmp = (unsigned int *)((uintptr_t)rlc_hdr +
        le32_to_cpu(rlc_hdr->reg_list_format_array_offset_bytes));  /* :704 */
for (i = 0 ; i < (reg_list_format_size_bytes >> 2); i++)
    register_list_format[i] = le32_to_cpu(tmp[i]);                  /* :706 */
```

`reg_list_format_array_offset_bytes` (`amdgpu_ucode.h:92`) is added to the
firmware buffer pointer with no validation against `fw->datasize`. A large
offset reads from beyond the firmware `kmalloc` → **OOB read / page fault
panic**. The same defect exists at line 711 for
`reg_list_array_offset_bytes`.

### `amdgpu_ucode_validate` does NOT prevent either bug

```c
/* amdgpu_ucode.c:251 */ int amdgpu_ucode_validate(const struct firmware *fw) {
    if (fw->datasize == le32_to_cpu(hdr->size_bytes)) return 0;
    return -EINVAL;
}
```

Only the top-level `datasize == size_bytes` is checked. Internal offsets and
sizes within the header are not validated, so a crafted firmware file
trivially passes validation.

Same class as DF-1133 (gfx_v7) and DF-1164 (gfx_v8).

## Reproducibility on the audit guest

`amdgpu` is `optional` (`sys/conf/files:2492+`); not in `X86_64_GENERIC`;
QEMU guest has no AMD GPU. The `amdgpu.ko` module exists at
`/boot/modules/amdgpu.ko` but cannot initialize without AMD hardware, so a
live trigger is not possible on this guest.

A userspace harness (`harness.c`) reconstructs the `kmalloc` + copy loops
and demonstrates both bugs with crafted firmware-header values:

```
=== Bug 1: u32 overflow in kmalloc(size_a + size_b) ===
  reg_list_format_size_bytes = 0x80000010 (2147483664)
  reg_list_size_bytes        = 0x80000010 (2147483664)
  u32 sum                    = 0x00000020 (32)   <-- WRAPS
  kmalloc(32); copy loop runs 0x20000004 iters, writing ~2 GB into 32-byte buffer
  -> structured heap overflow

=== Bug 2: unvalidated reg_list_format_array_offset_bytes ===
  fw->datasize = 4096; offset = 0x10000 (> datasize)
  BUG: OOB read faulted (signal 11) -- kernel equivalent: page fault past firmware
```

## Threat model

The firmware is loaded via `request_firmware` from `/lib/firmware/` (or
`amdgpufw_<chip>_rlc`). An attacker who can plant a crafted firmware file
(on systems that auto-load firmware, or via a malicious hypervisor that
presents forged firmware) triggers immediate slab corruption or a kernel
panic at GPU initialization.

Severity Medium (CVSS `PR:H`): requires either root on the target box to plant
firmware, or a malicious VM/hypervisor scenario. For an unprivileged attacker
with write access to `/lib/firmware` (a common misconfiguration), it becomes
High.

## Recommended fix

1. Compute the kmalloc size in `size_t` and reject on overflow.
2. Validate `reg_list_format_size_bytes + reg_list_format_array_offset_bytes
   <= fw->datasize` (and same for reg_list) before the copy loops.

See `fix.diff`.
