# DF-3852 — VERDICT

## Status: `inconclusive` (primitive proven in harness; kernel path hardware-gated, no radeon GPU on audit guest)

## Full narrative

### Where the bug is

`radeon_combios_get_power_modes()` parses the legacy COMBIOS POWERPLAY table
only on `RADEON_IS_MOBILITY` boards (`sys/dev/drm/radeon/radeon_combios.c:2720-2721`).
When the BIOS word at `offset+0x5` has bit 0x4 set, the parser builds a
VOLTAGE_GPIO record whose MMIO register index is taken **raw from the BIOS**
and multiplied by 4:

- `radeon_combios.c:2748-2752` (rev < 6):
  `gpio.reg = RBIOS16(offset + 0x5 + 0xb) * 4;  gpio.mask = (1 << tmp);`
- `radeon_combios.c:2754-2760` (rev >= 6, via `voltage_table_offset`):
  `gpio.reg = RBIOS16(voltage_table_offset) * 4;  gpio.mask = (1 << tmp);`

No range check. A 16-bit BIOS word gives `reg` anywhere in `0x0..0x3FFFC`.

### The sink

`r100_pm_misc()` (`sys/dev/drm/radeon/r100.c:352-364`) executes on every PM
clock transition for pre-AVIVO ASICs (`.misc = &r100_pm_misc`, radeon_asic.c:247+),
reached from `radeon_pm_set_clocks()` (`radeon_pm.c:219/241`):

```c
if ((voltage->type == VOLTAGE_GPIO) && (voltage->gpio.valid)) {
        tmp = RREG32(voltage->gpio.reg);        /* attacker-chosen offset */
        tmp |= voltage->gpio.mask;              /* or &= ~mask           */
        WREG32(voltage->gpio.reg, tmp);
```

The enabling bit is the *same* bit the combios parser tested:
`ATOM_PM_MISCINFO_VOLTAGE_DROP_SUPPORT == 0x00000004` (atombios.h:7620) ==
the parser's `misc & 0x4` — one crafted word turns the whole path on.

### Why this is out-of-BAR / out-of-range

`RREG32/WREG32` are `r100_mm_rreg/r100_mm_wreg` (radeon.h:2536-2539). For
`reg < rdev->rmmio_size` (the BAR, 64 KB on pre-AVIVO radeon) the access is a
direct `readl/writel` inside the mapping — the BIOS already picks **any**
register there for a chosen-bit RMW. For `reg >= rmmio_size` the access falls
to `r100_mm_rreg_slow/r100_mm_wreg_slow` (`r100.c:4125-4138`), which load the
raw offset into `RADEON_MM_INDEX` and hit `RADEON_MM_DATA` — an **indexed
device access outside the mapped window** with the attacker's full 18-bit
offset. Either way the VBIOS controls the target register of a kernel-issued
read-modify-write; combined with the UB mask (`1 << tmp`, tmp = BIOS byte,
garbage/any-bit for tmp >= 32 — see DF-3856) the write content is
attacker-chosen as well.

Not host-RAM corruption by itself: the primitive is device-space register
corruption (arbitrary GPU register RMW, incl. clock/aperture/window registers
via the indexed path), i.e. GPU hang / hardware misprogramming / potential
second-stage GART abuse by an unprivileged DRM client on the same node.

### Relationship to the known findings

- DF-1251 covers the **init-table loops** whose *OOB-read garbage* flows into
  WREG32 at driver init. This finding is different: **in-bounds** BIOS data
  (no over-read), parsed at init, *executed later at PM time*, whose **value**
  (a register index) is injected into RREG32/WREG32 with no validation.
- DF-1252 covers the *OOB read* of `voltage_table_offset` itself. Here the
  read is assumed in-bounds; the defect is the unvalidated register index and
  mask derived from it.

### Why `inconclusive` and not `reproduced`

The audit guest is virtio-only (`env.txt`): there is no radeon device, no
`rdev->rmmio`, no VBIOS source. The kernel path cannot be exercised. The
harness (`harness.c`) replicates the parser and the sink 1:1 and proves the
primitive deterministically (BIOS word 0xFFFF -> MMIO offset 0x3FFFC ->
out-of-BAR indexed write). On real hardware the same code path is
unconditional once the crafted table is present; nothing in the path depends
on anything but the BIOS bytes and the (chip-fixed) mobility flag.

### Fix

`fix.diff`: validate `gpio_reg < 0x10000` and `mask bit < 32` in both rev
paths; mark the record invalid otherwise. Apply-verified against the pristine
tree (`patch --dry-run` clean, see `fix_apply.log`). Runtime fix validation
not possible on this guest (no hardware): `fix_status = not_testable`.

## kernel_refs
- sys/dev/drm/radeon/radeon_combios.c:2739-2763 (parse)
- sys/dev/drm/radeon/radeon_combios.c:2748-2752, 2754-2760 (reg/mask assignment)
- sys/dev/drm/radeon/r100.c:345-364 (r100_pm_misc sink)
- sys/dev/drm/radeon/r100.c:4125-4138 (MM_INDEX slow path)
- sys/dev/drm/radeon/radeon_pm.c:219-241 (trigger)
- sys/dev/drm/radeon/atombios.h:7620 (0x4 = VOLTAGE_DROP_SUPPORT)

## 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.
