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)
PoC verification
Evidence pack
findings/poc/DF-4854 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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)
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=trueis set atvega10_ih.c:389amdgpu_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_maskat :210-213 - return is
wptr & ptr_maskat :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:308prescreen ignore-path,vega10_ih.c:352decode) 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_ivreadsring[ring_index + 0 .. +7]sys/dev/drm/amd/amdgpu/amdgpu_irq.c:159-176β callback also hands&ih->ring[ring_index](32 bytes) toamdgpu_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.
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
-
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). -
wptr source is device DMA β the writeback address programmed into
mmIH_RB_WPTR_ADDR_LO/HI(vega10_ih.c:127-132) isrb_dma_addr + wptr_offs*4= the first shadow word; HW (or a hostile GPU) writes it.vega10_ih.c:198-201reads it back;:219returnswptr & 0x3FFFFwith no 32-byte alignment validation. -
Spin β
amdgpu_ih.c:154-157: rptr is 0-phase (initamdgpu_ih.c:51, steps +32 atvega10_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 theatomic_xchgβ GPU interrupt delivery dead + 1 CPU spinning. (Matches DF-4843 with a 32-byte grid.) -
OOB read β register layout (
osssys_4_0_sh_mask.h:225-231): RB_OVERFLOW = bit 0, OFFSET = bits [17:2] (4-byte granular). Overflow pathvega10_ih.c:203-213clears bit 0 then setsrptr = (wptr+32) & 0x3FFFF. Device picks wptr = 0x3FFDC | 1 β rptr = 0x3FFFC β firstvega10_ih_decode_iv(vega10_ih.c:324-334) usesring_index = 0xFFFFand reads words0xFFFF..0x10006= bytes0x3FFFC..0x4001B; words0x10002..0x10006lie past the0x40008-byte allocation β 20-byte OOB read.amdgpu_irq_callbackadditionally passes&ring[0xFFFF]toamdgpu_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 << 1is upstream parity (amdgpu.h:457) - prescreen
#if 0kfifo 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_faultcapped at 128,amdgpu_vm.c:3561, freed only atamdgpu_vm_fini:3308;fault_creditonly 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-74clears 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) beforeamdgpu_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_testablefix 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)
Confirmed kernel references
- sys/dev/drm/amd/amdgpu/vega10_ih.c:194
- sys/dev/drm/amd/amdgpu/vega10_ih.c:210
- sys/dev/drm/amd/amdgpu/vega10_ih.c:219
- sys/dev/drm/amd/amdgpu/vega10_ih.c:308
- sys/dev/drm/amd/amdgpu/vega10_ih.c:327
- sys/dev/drm/amd/amdgpu/vega10_ih.c:352
- sys/dev/drm/amd/amdgpu/vega10_ih.c:389
- sys/dev/drm/amd/amdgpu/amdgpu_ih.c:61
- sys/dev/drm/amd/amdgpu/amdgpu_ih.c:154
- sys/dev/drm/amd/amdgpu/amdgpu_irq.c:170
- sys/dev/drm/amd/include/asic_reg/oss/osssys_4_0_sh_mask.h:229
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.
No comments yet.