# DF-1176 — VERDICT

**Verdict: REPRODUCED (source+harness, module-build-validated).** Not `uid=0`
on the audit guest — this is a **memory-corruption primitive (structured heap
overflow + OOB read)**, but the only realistic trigger paths require root or
a malicious hypervisor, so there is **no unprivileged privilege boundary to
cross**. Valid hard blocker per Phase 6 (root-only reachability).

## Bug confirmation

`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 validation. Two
distinct bugs:

### 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);  /* u32 from disk */
adev->gfx.rlc.reg_list_size_bytes =
        le32_to_cpu(rlc_hdr->reg_list_size_bytes);         /* u32 from disk */
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);
```

Both fields are `uint32_t` (`amdgpu_gfx.h:76-77`, populated from
`amdgpu_ucode.h:91,93`). The sum is computed in 32-bit arithmetic. A crafted
firmware with `0x80000010 + 0x80000010` wraps to `0x20` (32 bytes), so
`kmalloc(32)` is called and the subsequent copy loops (lines 705-706,
712-713) write ~2 GB into a 32-byte allocation → **structured slab
corruption / heap overflow**.

### 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));
for (i = 0 ; i < (reg_list_format_size_bytes >> 2); i++)
    register_list_format[i] = le32_to_cpu(tmp[i]);
```

`reg_list_format_array_offset_bytes` and `reg_list_array_offset_bytes`
(`amdgpu_ucode.h:92,94`) are added to the firmware pointer with no validation
against `fw->datasize. A large offset reads past the firmware `kmalloc` →
**OOB read / page-fault panic**.

### `amdgpu_ucode_validate` does NOT prevent either bug

`amdgpu_ucode.c:251` only checks `fw->datasize == hdr->size_bytes`. Internal
offsets and sizes are not validated, so a crafted firmware trivially passes
validation.

## 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/kernel/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:

```
=== Bug 1: u32 overflow in kmalloc(size_a + size_b) ===
  size_a=0x80000010 size_b=0x80000010 u32_sum=0x20 (32)  <-- WRAPS
  kmalloc(32); copy loop runs 536870916 iterations, writing 2147483664 bytes
  -> 2147483632-byte heap overflow past kmalloc'd buffer
=== 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
```

## Module build validation

The fix.diff was applied in-guest to `/usr/src/sys/dev/drm/amd/amdgpu/gfx_v9_0.c`
and `make` in `/usr/src/sys/dev/drm/amd/amdgpu` compiled the patched
`gfx_v9_0.c` cleanly (`/usr/obj/usr/src/sys/dev/drm/amd/amdgpu/gfx_v9_0.o`
produced, no warnings, `-Werror`). The full amdgpu.ko link step fails on an
**unrelated** pre-existing issue (`color_gamma.o: file is empty` in
`display/dc/color_gamma.c`); this is not related to our patch. Source was
reverted after validation.

## Exploit chain

`blocked by valid hard blocker (root-only reachability)`: The vulnerable
parsing runs in `request_firmware` context — the firmware is loaded from
`/lib/firmware/amdgpufw_<chip>_rlc` by the kernel. Triggering requires either:

1. **Root planting a crafted firmware file** (root → kernel is game-over by
   definition; no privilege boundary to cross), or
2. **A malicious hypervisor** presenting forged firmware to a guest VM
   (out-of-scope for local privilege escalation).

There is **no unprivileged local path** to deliver a crafted RLC firmware on
the audit guest: firmware files live under `/lib/firmware/` (root-owned) and
are loaded by the kernel only at GPU probe time, which itself requires AMD
hardware (absent on the QEMU guest). The primitive is real and would be a
strong escalation candidate on a system where it IS reachable (a multi-user
box with world-writable `/lib/firmware`, common in some container/VM images),
but on the default DragonFlyBSD deployment the boundary is absent.

Concrete next iteration if an unprivileged firmware-write path were found:
the slab-corruption primitive (Bug 1) lands in the DRM slab bucket — groom
adjacent `struct file` / `struct ucred` objects, overflow into a victim, and
redirect a corrupted `ucred *` to a forged credential in userspace (no SMAP,
no SMEP). This is exactly the chain developed for the analogous DF-0783-class
bugs.

## Fix

`fix.diff` introduces a `size_t`-computed allocation with overflow check, and
validates both offsets and sizes against `fw->datasize` before the copy loops.
`git apply --check` passes; patched source compiles cleanly into
`gfx_v9_0.o` on the guest.

## Threat model

Severity Medium (CVSS `PR:H`): realistically triggerable by root on a system
with AMD GPU, or by a malicious hypervisor. High for misconfigured systems
where `/lib/firmware` is world-writable.

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