# DF-1252 — Unvalidated 16-bit BIOS offsets dereferenced in LVDS mode-table and GPIO voltage-table parsing

## Verdict
**SOURCE-CONFIRMED (real bug), INCONCLUSIVE at runtime** — the `radeon.ko` module is not loaded (no AMD GPU with legacy COMBIOS in the QEMU guest) and is not in the GENERIC kernel, so the unvalidated-offset paths are dormant. Fix authored and compile-validated.

## Mechanism (source trace)
`RBIOS16(i)` / `RBIOS8(i)` are raw byte-indexing macros into the cached BIOS blob with **no bounds check** (`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))
```
`rdev->bios` is a `kmalloc(size, …)` blob (`radeon_bios.c:68,98,269,717`) whose `size` is the ROM image length (variable; never stored on the struct). Two parser paths read a 16-bit offset from the BIOS and then dereference it without validating it lies within the blob:

**Path 1 — LVDS native-mode table** (`radeon_combios.c:1246-1272`):
```
for (i = 0; i < 32; i++) {
    tmp = RBIOS16(lcd_info + 64 + i * 2);   /* tmp = attacker/BIOS-controlled 16-bit offset */
    if (tmp == 0) break;
    /* NO bounds check that tmp < bios_size */
    if ((RBIOS16(tmp) == hdisplay) &&        /* derefs bios[tmp], bios[tmp+1] */
        (RBIOS16(tmp + 2) == vdisplay)) {     /* derefs bios[tmp+2], bios[tmp+3] */
        ... RBIOS16(tmp + 17), RBIOS16(tmp+19), RBIOS16(tmp+21), RBIOS8(tmp+23),
            RBIOS16(tmp+24), RBIOS16(tmp+26), RBIOS16(tmp+28), RBIOS16(tmp+9) ...
```
With `tmp` up to 0xFFFF and the BIOS blob often < 64KB, every `RBIOS*(tmp+N)` reads up to ~64KB past the allocation → kernel heap OOB read. The leaked u32s land in `lvds->native_mode` (htotal/vtotal/clock/etc.) which are exposed to userspace via the DRM mode-info ioctl.

**Path 2 — GPIO voltage table** (`radeon_combios.c:2754-2760`):
```
u16 voltage_table_offset = RBIOS16(offset + 0x5 + 0xc);   /* unchecked 16-bit offset */
if (entries && voltage_table_offset) {
    ... RBIOS16(voltage_table_offset) * 4;                 /* derefs bios[vto], bios[vto+1] */
        RBIOS8(voltage_table_offset + 0x2);                /* derefs bios[vto+2] */
```
Same OOB-read pattern.

## Why not reproduced at runtime
- `radeon.ko` is **not loaded** on the guest and is **not in X86_64_GENERIC**.
- Requires an AMD Radeon GPU with a legacy COMBIOS (pre-AtomBIOS ATI chips) attached; QEMU guest has no AMD GPU.
- The BIOS image is read from the card's ROM at probe time; a malicious PCI card (VFIO passthrough) could supply a crafted ROM. Not unprivileged-user-triggered.

## Fix (fix.diff, compile-validated)
1. Add `size_t bios_size;` to `struct radeon_device` (`radeon.h`) and a `radeon_bios_in(rdev, off, len)` bounds-check helper.
2. Set `rdev->bios_size` at every allocation site in `radeon_bios.c` (and clear it on the NULL error paths).
3. Guard both cited paths in `radeon_combios.c`: skip the entry if `!radeon_bios_in(rdev, tmp, 30)` (LVDS) / `!radeon_bios_in(rdev, voltage_table_offset, 3)` (voltage table).

The patched `radeon.h`, `radeon_bios.c`, and `radeon_combios.c` all compile cleanly with gcc 8.3, `-Werror`, no warnings.

## Realistic impact ceiling
Kernel heap OOB read (up to ~64KB) of attacker/BIOS-influenced data, with partial leak to userspace via DRM mode ioctls, triggered by a malicious PCI card's crafted ROM at GPU probe. On this guest: **not reachable** (no AMD GPU). Driver/hardware-trust hardening fix.
