# DF-1251 — radeon COMBIOS table-walk loops over-read BIOS + write GPU MMIO

## Verdict (one line)
**CONFIRMED REAL (source trace + harness primitive), NOT reproduced on audit guest (no AMD/ATI radeon GPU).**

## Finding
`sys/dev/drm/radeon/radeon_combios.c` parses legacy COMBIOS tables with
`while (RBIOS16(offset))` / `while (RBIOS8(offset))` loops that advance
`offset` purely from BIOS content and have **no upper bound** against the
BIOS allocation. A malicious Video BIOS whose table never contains a zero
terminator inside the allocation drives `RBIOS8/16/32` past `rdev->bios`
into adjacent kernel heap, and those heap-sourced `addr`/`val` pairs are
written to GPU MMIO via `WREG32` — arbitrary GPU register writes.

## Mechanism (path:line)
1. `sys/dev/drm/radeon/radeon.h:2686-2688`:
   `#define RBIOS8(i)  (rdev->bios[i])`
   `#define RBIOS16(i) (RBIOS8(i) | (RBIOS8((i)+1) << 8))`
   `#define RBIOS32(i) ((RBIOS16(i)) | (RBIOS16((i)+2) << 16))`
   — direct array index, no bounds.
2. `radeon_combios.c:3010` `combios_parse_mmio_table`:
   `while (RBIOS16(offset)) { ... addr = RBIOS16(offset) & 0x1fff; ... val = RBIOS32(offset); ... WREG32(addr, val); }`
3. `radeon_combios.c:3089` `combios_parse_pll_table`:
   `while (RBIOS8(offset)) { ... WREG32_PLL(addr, val); }`
4. `radeon_combios.c:3182` `combios_parse_ram_reset_table`:
   `while (val != 0xff) { ... RBIOS16(offset); ... }`
5. None of these bounds `offset` against the BIOS allocation size, so the
   loop walks past `rdev->bios` (which can be as small as 512 bytes) into
   heap, and the heap-sourced values are written to GPU MMIO (`WREG32`).

## Why not reproduced on the audit guest
`radeon` is a loadable DRM module, **not in GENERIC**, and the guest has no
AMD/ATI GPU. COMBIOS tables are parsed during GPU attach from the card's
Video BIOS, which is absent. Realistic trigger: a malicious GPU Video BIOS
(e.g. hotplug PCIe card with a crafted ROM). Legitimate "malicious
peripheral firmware" threat, not exercisable on this guest.

## Primitive proof (harness)
`harness.c` models a 512-byte BIOS slab whose mmio table has no in-bounds
zero terminator and runs a verbatim `combios_parse_mmio_table` walk:
```
BIOS allocation = 512 bytes; table starts at 0x40 (no in-bounds terminator)
RBIOS8/16/32 reads past BIOS allocation (OOB): 2
WREG32 GPU-MMIO writes with heap-sourced addr/val: 75
PRIMITIVE CONFIRMED: unbounded loop read 2 words past rdev->bios into heap,
and wrote 75 (addr,val) pairs to GPU MMIO (WREG32). Bug is REAL.
```

## Fix
`fix.diff` (3 files):
- `radeon.h`: add `uint32_t bios_length;`.
- `radeon_bios.c`: set `rdev->bios_length = size;` at every alloc site.
- `radeon_combios.c`: bound the three table-walk loops so they stop when
  `offset + <max access>` would exceed `rdev->bios_length`.
**Validated: builds cleanly into `radeon.ko` with `-Werror` (incremental
rebuild of the changed `radeon_combios.c` succeeded).** (DF-1250's fix
shares the same `bios_length` infrastructure.)

## Reproduce
```
ssh dfbsd-maxx; cd poc/DF-1251 && cc -O2 -Wall -o harness harness.c && ./harness
```
