# DF-4854 — vega10_ih: unvalidated device-DMA'd IH wptr → permanent interrupt-thread spin + 20-byte OOB ring read on overflow recovery

Twin of **DF-4843** (tonga_ih), filed for `sys/dev/drm/amd/amdgpu/vega10_ih.c`
(pass 2, GLM 5.3). One aggravator the tonga twin does not have: on Vega10 the
32-byte-entry decode at the top of the 256 KiB ring reads **past the end of the
DMA allocation** when the device sets RB_OVERFLOW with a high wptr.

## Where (all citations into the audit tree)

- `sys/dev/drm/amd/amdgpu/vega10_ih.c:194-220` — `vega10_ih_get_wptr()`
  - wptr read from device-DMA'd memory: `ih.ring[ih.wptr_offs]`
    (`use_bus_addr=true` is set at `vega10_ih.c:389`
    `amdgpu_ih_ring_init(adev, &adev->irq.ih, 256*1024, true)`)
  - RB_OVERFLOW (bit 0, `IH_RB_WPTR__RB_OVERFLOW_MASK 0x1`,
    `sys/dev/drm/amd/include/asic_reg/oss/osssys_4_0_sh_mask.h:229`) is handled
    at :203-218, recovery rptr = `(wptr + 32) & ptr_mask` at :210-213
  - return is `wptr & ptr_mask` at :219 — **alignment (mod 32) never validated**
- `sys/dev/drm/amd/amdgpu/amdgpu_ih.c:154-157` — the walk:
  `while (ih->rptr != wptr) { callback(); ih->rptr &= ih->ptr_mask; }`
  - rptr advances strictly in 32-byte steps (`vega10_ih.c:308` prescreen
    ignore-path, `vega10_ih.c:352` decode)
- `sys/dev/drm/amd/amdgpu/amdgpu_ih.c:61-68` — the allocation:
  `dma_alloc_coherent(ring_size + 8)` = 0x40008 bytes; the +8 tail holds the
  wptr/rptr shadow words (indices 0x10000/0x10001)
- `sys/dev/drm/amd/amdgpu/vega10_ih.c:327-334` — `decode_iv` reads
  `ring[ring_index + 0 .. +7]`
- `sys/dev/drm/amd/amdgpu/amdgpu_irq.c:159-176` — callback also hands
  `&ih->ring[ring_index]` (32 bytes) to `amdgpu_amdkfd_interrupt()` at :170

## Primitive 1 — permanent interrupt-thread spin (DoS)

rptr starts at 0 and only ever advances by 32 (then masked). If the device
writes a wptr that is not a multiple of 32 (e.g. 0x10004), `rptr != wptr`
holds for every iteration: rptr laps the 256 KiB ring forever inside
`amdgpu_irq_handler` with `ih->lock` held (`amdgpu_ih.c:144`,
`atomic_xchg`; it is only reset at :160 after loop exit). Every subsequent
amdgpu interrupt hits the held lock and returns IRQ_NONE — all GPU interrupt
delivery is dead, one CPU pegged. Same argument as DF-4843 with a 32-byte grid
instead of 16.

## Primitive 2 — 20-byte OOB read past the ring allocation

Overflow-recovery rptr keeps the (4-granular, `IH_RB_WPTR__OFFSET_MASK
0x0003FFFC`, sh_mask.h:230) phase of the device wptr. Pick wptr = 0x3FFDC with
RB_OVERFLOW set: rptr := (0x3FFDC + 0x20) & 0x3FFFF = **0x3FFFC**. First
decode then uses `ring_index = 0x3FFFC >> 2 = 0xFFFF` and reads words
`0xFFFF..0x10006` (`vega10_ih.c:327-334`), i.e. byte offsets
`0x3FFFC..0x4001B`. The allocation ends at `0x40008`, so words
`0x10002..0x10006` (byte range `0x40008..0x4001B`, **20 bytes**) are read
out of bounds of the `dma_alloc_coherent` block. The OOB dwords become
`entry->src_data[]` / timestamp / pasid fields and are dispatched
(`amdgpu_irq.c:170,173-175`); dispatch indices are bounds-checked downstream
(`amdgpu_irq.c:409-417`), so the OOB content is consumed/logged in-kernel
(not directly returned to userspace — kfd pass-through at
`amdgpu_irq.c:170` receives the same OOB window).

## Threat model

Hostile / faulting GPU (the DF-4755/4756 mxgpu model) or a desynced writeback:
the wptr shadow is plain device-writable DMA memory. Upstream-inherited shape
(upstream vega10_ih_get_wptr also only masks). No userspace-only trigger.

## Reproduce / verify

Not runnable on the audit guest: it has no Vega10 (or any) AMD GPU, and the
wptr source is device DMA. The trigger sketch (`trigger_sketch.c`) documents
the required hostile-device writes. Static proof above is exact arithmetic on
the register definitions; see `VERDICT.md`.

## Recommended fix

Validate entry alignment in `vega10_ih_get_wptr` (round a misaligned wptr
down to the 32-byte grid with a warn, and mask the overflow-recovery rptr the
same way), and cap the per-invocation entry count in `amdgpu_ih_process`.
