β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-4854

vega10_ih: unvalidated device-DMA'd IH wptr (DF-4843 twin) β€” permanent amdgpu interrupt-thread spin on misaligned wptr, plus 20-byte OOB ring read via RB_OVERFLOW recovery

Summary

The wptr comes from device-DMA'd writeback memory and is only masked - 32-byte alignment never validated, so a misaligned wptr can never equal the 32-byte-stepping rptr: the ithread laps the 256KiB ring forever with ih->lock held (all GPU IRQs dead, one CPU pegged). Aggravator unique to vega10: the RB_OVERFLOW recovery rptr=(wptr+32)&mask preserves the device wptr's 4-granular phase - wptr=0x3FFDC|1 yields decode reads 20 bytes PAST the ring_size+8 coherent allocation, interpreted as an IV and handed to the kfd forwarder. Upstream-inherited shape; hostile/faulting-GPU model; no GPU on guest. Validate-alignment + cap-per-invocation fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4854 Β· 5 files
FileTypeDescriptionSize
README.md β€” 4.0 KB ↓ raw
VERDICT.md β€” 4.7 KB ↓ raw
verdict.json β€” 3.2 KB view raw
manifest.json β€” 1.5 KB view raw
trigger_sketch.c β€” 1.7 KB view raw

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)

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.

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.

VERDICT.md
↓ download raw

DF-4854 VERDICT β€” vega10_ih unvalidated wptr (DF-4843 twin) + overflow-recovery OOB read

Status: untested (hardware-bound β€” no AMD GPU on the audit guest). Confidence: certain (static proof, exact register arithmetic).

Why untested

The primitive requires the GPU's IH block to write a malformed wptr into the DMA'd writeback slot (ih.ring[ih.wptr_offs], vega10_ih.c:199). The QEMU audit guest (DragonFly dfbsd 6.5-DEVELOPMENT #0 ... X86_64_GENERIC) has no AMD GPU; there is no unprivileged-software path that forges the wptr. Same disposition as the tonga twin DF-4843 (untested / dos / certain).

Static proof

  1. Ring geometry β€” vega10_ih.c:389: amdgpu_ih_ring_init(..., 256*1024, true) β†’ amdgpu_ih.c:47-50: rb_bufsz = order_base_2(0x40000/4) = 16, ring_size = (1<<16)*4 = 0x40000, ptr_mask = 0x3FFFF; amdgpu_ih.c:61-68: dma_alloc_coherent(0x40000 + 8), wptr_offs = 0x10000, rptr_offs = 0x10001 (the +8 tail).

  2. wptr source is device DMA β€” the writeback address programmed into mmIH_RB_WPTR_ADDR_LO/HI (vega10_ih.c:127-132) is rb_dma_addr + wptr_offs*4 = the first shadow word; HW (or a hostile GPU) writes it. vega10_ih.c:198-201 reads it back; :219 returns wptr & 0x3FFFF with no 32-byte alignment validation.

  3. Spin β€” amdgpu_ih.c:154-157: rptr is 0-phase (init amdgpu_ih.c:51, steps +32 at vega10_ih.c:308/:352). wptr ≑ k (mod 32), k β‰  0 β‡’ loop never terminates; ih->lock (amdgpu_ih.c:144) never released β‡’ every later interrupt returns IRQ_NONE at the atomic_xchg β‡’ GPU interrupt delivery dead + 1 CPU spinning. (Matches DF-4843 with a 32-byte grid.)

  4. OOB read β€” register layout (osssys_4_0_sh_mask.h:225-231): RB_OVERFLOW = bit 0, OFFSET = bits [17:2] (4-byte granular). Overflow path vega10_ih.c:203-213 clears bit 0 then sets rptr = (wptr+32) & 0x3FFFF. Device picks wptr = 0x3FFDC | 1 β‡’ rptr = 0x3FFFC β‡’ first vega10_ih_decode_iv (vega10_ih.c:324-334) uses ring_index = 0xFFFF and reads words 0xFFFF..0x10006 = bytes 0x3FFFC..0x4001B; words 0x10002..0x10006 lie past the 0x40008-byte allocation β‡’ 20-byte OOB read. amdgpu_irq_callback additionally passes &ring[0xFFFF] to amdgpu_amdkfd_interrupt (amdgpu_irq.c:170) β€” same window. Downstream dispatch indexes are bounds-checked (amdgpu_irq.c:409-417), so impact is kernel-consumed OOB data (log fields / IV decode), not a demonstrated userspace leak.

Exploit chain

None (device-gated). Ceiling: hostile-GPU DoS (primitive 1) + hostile-GPU OOB read of ≀20 bytes of adjacent coherent DMA memory interpreted as an IV (primitive 2).

Kill list for the rest of the file (pass-2 negative evidence)

  • dispatch client_id/src_id bounds-checked: amdgpu_irq.c:409-417
  • normal-path decode is exactly in-bounds: max phase-0 rptr = 0x3FFE0 β‡’ words 0xFFF8..0xFFFF β€” inside the ring; only the overflow path breaks it
  • ring-init wb/doorbell math correct (amdgpu_ih.c:61-68, vega10_ih.c:127-148); AMDGPU_DOORBELL64_IH << 1 is upstream parity (amdgpu.h:457)
  • prescreen #if 0 kfifo stub (vega10_ih.c:288-301): the kprintf half is DF-1936 (known); the functional half (all retry faults dropped, per-VM fault hash never drained β€” amdgpu_vm_add_fault capped at 128, amdgpu_vm.c:3561, freed only at amdgpu_vm_fini:3308; fault_credit only ever decremented, amdgpu_vm.c:3390) is an upstream-divergence robustness issue with no memory-safety consequence β€” negative note, not a finding
  • stale wptr shadow across disable/enable (vega10_ih.c:69-74 clears HW registers, not the DMA'd shadow): upstream-parity, subsumed by the unvalidated-wptr finding under the hostile-GPU model
  • fini ordering correct: amdgpu_irq_fini (drm_irq_uninstall/free_irq) before amdgpu_ih_ring_fini (vega10_ih.c:405-406)

Fix

fix.diff intentionally omitted per policy for untested findings (same as DF-4843). Recommended shape: in vega10_ih_get_wptr, after masking, round a misaligned wptr down to the 32-byte entry boundary with a dev_warn (and apply the same alignment mask to the overflow-recovery rptr); cap the per-invocation entry count in amdgpu_ih_process so a bad wptr/storm cannot hard-spin the interrupt thread.

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.

(No fix.diff for this item: documented recommendation only - see Fix above.)

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

fix validation not possible: finding is device-gated (no Vega10 GPU on the audit guest); recommended fix is static-only, mirroring DF-4843

VERDICT.md 'Fix' section; no fix_run.log/fix_build.log (no baseline repro possible)
per-fix-DF-4854

Confirmed kernel references

Detail

Evidence (decisive lines)

README.md (exact register arithmetic with path:line); VERDICT.md (proof + kill list); trigger_sketch.c (hostile-device write pattern)

PoC changes

sketch only; no runnable PoC without a Vega10 GPU (hostile-device DMA source)

Verified recommended fix

in vega10_ih_get_wptr, after masking, round a misaligned wptr down to the 32-byte entry boundary with a warn (and align the overflow-recovery rptr identically); cap per-invocation entries in amdgpu_ih_process so a bad wptr cannot hard-spin the interrupt thread

Verdict

Not runnable on the audit guest (no AMD GPU; the wptr comes from device DMA into the writeback shadow word). Code-certain by inspection: vega10_ih_get_wptr (vega10_ih.c:194-220) returns the device-DMA'd wptr with only a ptr_mask and never validates 32-byte entry alignment, and amdgpu_ih_process (amdgpu_ih.c:154-157) walks with rptr stepping strictly 32 bytes. A misaligned wptr can never equal rptr: the amdgpu interrupt thread laps the 256 KiB ring forever with ih->lock held (amdgpu_ih.c:144) - all GPU interrupt delivery dead (DF-4843 twin with a 32-byte grid). Aggravator unique to this file: the RB_OVERFLOW recovery path (vega10_ih.c:210-213) keeps the wptr's 4-granular phase, so wptr=0x3FFDC|1 yields rptr=0x3FFFC and the first decode_iv (vega10_ih.c:327-334) reads ring words 0xFFFF..0x10006 - words 0x10002..0x10006 are 20 bytes past the ring_size+8 dma_alloc_coherent allocation (amdgpu_ih.c:61-68) - a hostile-GPU OOB read whose contents are decoded as an IV and also handed to amdgpu_amdkfd_interrupt (amdgpu_irq.c:170). Dispatch indices themselves are bounds-checked (amdgpu_irq.c:409-417). Hostile/faulting-device DoS + bounded OOB read; no uid0 route.