# DF-2219 — Unbounded array index in encode_pcie_lane_width() (drm/amd powerplay)

## Verdict: NOT REPRODUCED (HW-gated); REAL BUG IN SOURCE (defense-in-depth fix warranted)

## Hardware gate (why the PoC cannot run on this guest)

`encode_pcie_lane_width()` is part of the AMDGPU powerplay (PowerPlay/SMU)
subsystem and is reached only when an AMD GPU DRM driver attaches and parses the
card's VBIOS PowerPlay table. The audit QEMU/KVM guest has **no AMD GPU** and no
DRM driver:

```
$ pciconf -l | grep -iE "0x030000|0x038000|vga|amd|1002"
vgapci0@pci0:0:2:0:  class=0x030000 chip=0x11111234 rev=0x02   # Bochs/QEMU std VGA, not AMD
$ ls /dev/dri* /dev/drm*            # No such file or directory
$ kldstat | grep -iE "drm|amdgpu"   # (none)
```

With no AMD GPU and no attached AMDGPU driver, the PowerPlay-table parsing that
feeds `lane_width` into `encode_pcie_lane_width()` never runs on this guest. The
unprivileged `maxx` user has no path to supply a crafted VBIOS.

(The README's PoC suggestion — `cc -DKLD test_encode_oob.c; kldload` — is
explicitly rejected by the bright-line rule: `kldload` requires root and a
root-loaded module can prove nothing about unpriv→root. It is not a valid PoC.)

## Source trace — the bug is REAL (sys/dev/drm/amd/powerplay/hwmgr/pppcielanes.c)

```c
static const unsigned char pp_r600_encode_lanes[] = { /* 17 entries: indices 0..16 */
    0,1,2,0,3,0,0,0,4,0,0,0,5,0,0,0,6 };
static const unsigned char pp_r600_decoded_lanes[8] = { 16,1,2,4,8,12,16, };

uint8_t encode_pcie_lane_width(uint32_t num_lanes)  /* pppcielanesc:56 */
{
    return pp_r600_encode_lanes[num_lanes];   /* NO bounds check */
}
uint8_t decode_pcie_lane_width(uint32_t num_lanes)  /* pppcielanesc:61 */
{
    return pp_r600_decoded_lanes[num_lanes];  /* NO bounds check */
}
```

Callers pass attacker-influenced VBIOS values straight through, e.g.:
- `vega10_hwmgr.c:1244`:
  `encode_pcie_lane_width(bios_pcie_table->entries[i].lane_width)` where
  `lane_width` is populated from the VBIOS `ucPCIELaneWidth` (`UCHAR`/`uint8_t`,
  values 0-255) at `vega10_processpptables.c:795` with **no validation**.
- `ci_smumgr.c:1007`, `tonga_smumgr.c:518`, `iceland_smumgr.c:776`,
  `polaris10_smumgr.c:778`, `fiji_smumgr.c:841`, `vegam_smumgr.c:582` likewise
  feed `param1`/`lane_width` from device-controlled tables.

For any `num_lanes > 16`, `pp_r600_encode_lanes[num_lanes]` reads up to 238
bytes past the 17-element table into adjacent kernel `.rodata`. The leaked byte
is then written into `LinkLevel[i].PcieLaneCount` and uploaded to the GPU SMC
coprocessor — a small info-leak plus an out-of-spec lane width that can hang the
GPU / panic the system. (The decode side is currently safe because its sole
caller `smu7_hwmgr.c:167` enforces `link_width <= 7`.)

Attacker: a malicious PCIe device (hot-plug Thunderbolt/USB4/server hot-swap/
SR-IOV vGPU pass-through) presenting AMD vendor/device IDs with a crafted
expansion ROM. Read-only primitive; no write.

## Exploit chain status

Not pursuable — read-only OOB `.rodata` read primitive behind absent AMD GPU
hardware (valid Phase-6 hard blocker: read-only primitive + dead path at
runtime). No escalation chain derivable.

## PoC changes

None. The README's `kldload` PoC is invalid (bright-line rule); no DRM/AMD GPU
on guest. Verified by source trace only.

## Recommended fix

Bounds-check `num_lanes` against the table size (return 0 / a safe default for
out-of-range). See `fix.diff` (matches finding proposal intent; the encode side
is the reachable one).
