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

cik_ih: unvalidated device-DMA'd IH wptr β€” DF-4843/DF-4854 twin on the 16-byte CIK grid: a misaligned wptr permanently hard-spins the amdgpu interrupt thread, and the RB_OVERFLOW recovery preserves the bad phase so wptr∈[0xFFE4..0xFFEF]|OVERFLOW parks rptr at [0xFFF4..0xFFFF] β†’ up to 12-byte OOB read past the zero-slack 64 KiB GTT ring BO

Summary

Same shape as the tonga/vega10 twins (same wb wptr source, +16 recovery): a non-16-multiple wptr can never equal the 16-byte-stepping rptr - the ithread laps the ring forever with ih->lock held (all GPU IRQs dead); the RB_OVERFLOW recovery imports the bad 4-granular phase so the first decode reads up to 12 bytes past the EXACT-SIZE GTT ring BO (CIK has no +8 slack). Hostile/faulting-device model; no AMD GPU on guest. Align-normalize + cap-the-walk fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4887 Β· 7 files
FileTypeDescriptionSize
README.md β€” 4.1 KB ↓ raw
VERDICT.md β€” 4.7 KB ↓ raw
trigger_sketch.c β€” 1.7 KB view raw
fix.diff β€” 2.4 KB view raw
verdict.json β€” 4.8 KB view raw
manifest.json β€” 1.3 KB view raw
env.txt β€” 333 B view raw

DF-4887 β€” cik_ih: unvalidated device-DMA'd IH wptr (DF-4843 twin, 16-byte grid) + phase-preserving RB_OVERFLOW recovery parking rptr within 12 bytes of ring end β†’ up to 12-byte OOB read past the 64 KiB GTT ring BO

File: sys/dev/drm/amd/amdgpu/cik_ih.c:186-206 (with the shared walk at amdgpu_ih.c:140-165).

Answers to the batch's specific cik questions

  • wptr source: cik DOES use the writeback buffer, same as tonga β€” wptr = le32_to_cpu(adev->wb.wb[adev->irq.ih.wptr_offs]) (cik_ih.c:190); cik_ih_sw_init passes use_bus_addr=false (cik_ih.c:321) so the ring[]-embedded shadow path never runs. NOT a different shape: this is the DF-4843 shape on a 16-byte IV grid.
  • doorbell: none β€” CIK predates IH doorbells. The funcs table (cik_ih.c:462-467) has only get_wptr/prescreen_iv/decode_iv/set_rptr, and set_rptr uses the mmIH_RB_RPTR register (cik_ih.c:297-300).
  • RB_OVERFLOW path: present and same-phase as tonga: adev->irq.ih.rptr = (wptr + 16) & adev->irq.ih.ptr_mask (cik_ih.c:200) β€” the DF-4854 aggravator exists here too (12-byte variant, worse in one respect: the GTT BO allocation has NO +8 slack).

The defect

cik_ih_get_wptr consumes the GPU-writeback u32 with nothing but & ptr_mask (cik_ih.c:205). The consumer walk (amdgpu_ih_process, amdgpu_ih.c:154-157) terminates on rptr == wptr while rptr advances strictly in 16-byte steps (+= 16 at cik_ih.c:255 prescreen-drop and cik_ih.c:287 decode). Two consequences for a wptr that is not a multiple of 16 (hostile/faulting firmware DMA, corrupted writeback after a PCIe fault):

  1. Permanent interrupt-thread spin (the DF-4843 core). With the overflow bit clear, rptr (phase 0) can never equal a phase-p wptr: the ithread laps the 64 KiB ring forever β€” 4096 IVs re-dispatched per lap β€” with ih->lock held (amdgpu_ih.c:144) and restart_ih re-reading the same bad wptr (amdgpu_ih.c:163-165). One CPU pegged in interrupt context, all GPU interrupt delivery dead.
  2. OOB read aggravator (the DF-4854 shape, 12-byte variant). With the overflow bit SET and wptr' (overflow-cleared) in [0xFFE4..0xFFEF] (sub-16 phase 4..15 near ring end), the recovery rptr = (wptr' + 16) & 0xFFFF lands in [0xFFF4..0xFFFF] β€” within 12 bytes of the ring end. The first prescreen_iv/decode_iv then reads ring[ring_index .. ring_index + 3] with ring_index = rptr>>2 up to 0x3FFF (cik_ih.c:243-246, 274-277) β†’ dwords 0x4000..0x4002 = up to 12 bytes past the 64 KiB ring buffer. The ring here is a amdgpu_bo_create_kernel GTT BO of exactly ring_size (amdgpu_ih.c:80-88) β€” unlike vega10's ring_size+8 dma_alloc_coherent, there is zero slack: the read touches the next mapped page (adjacent kernel memory interpreted as an IV and handed to amdgpu_amdkfd_interrupt at amdgpu_irq.c:170) or faults outright. The garbage IV is then decoded and dispatched to IP-block handlers (dispatch indices themselves are bounds-checked in amdgpu_irq.c, so the blast radius is the 12-byte OOB read + bogus dispatch).

Prescreen/PASID (cik_ih.c:238-257): same fault-credit logic as tonga β€” amdgpu_vm_pasid_fault_credit (amdgpu_vm.c:3370-3397) is idr_find under pasid_lock; the drop path advances rptr by 16 consistently with the walk. No cik-unique defect there.

Threat model / severity

Hostile or faulting GPU device (the wptr source is device DMA into host writeback memory). No unprivileged-software trigger; hardware-gated DoS + bounded OOB read. Same Low grading as DF-4843/DF-4854.

Reproduce

Not runnable on the audit guest: guest PCI tree has no AMD GPU (env.txt), and the trigger requires device-side DMA of a misaligned wptr. Code-certain by inspection (arithmetic above). See trigger_sketch.c for the hostile write pattern that would be needed.

Fix

In cik_ih_get_wptr, after clearing the overflow bit, normalize a misaligned wptr down to the 16-byte entry boundary (wptr &= ~0xF; after masking, with a dev_warn), and compute the overflow-recovery rptr from the aligned value β€” plus a per-invocation entry cap in amdgpu_ih_process so a storm or bad wptr can never hard-spin the ithread.

VERDICT.md
↓ download raw

DF-4887 VERDICT

Status: untested (device-gated; the audit guest has no AMD GPU β€” pciconf -l shows no AMD/ATI device, env.txt). Identical treatment to the just-filed twins DF-4843 (tonga_ih.c) and DF-4854 (vega10_ih.c): the defect's data source is device DMA into host memory, which no guest without the hardware can produce.

Certain by inspection

  1. wptr source is the writeback buffer, same as tonga β€” this is the third instance of the family, not a different shape. cik_ih_get_wptr (cik_ih.c:190) reads adev->wb.wb[adev->irq.ih.wptr_offs]; use_bus_addr is false for CIK (cik_ih.c:321), so only the wb path exists. Returned with nothing but wptr & adev->irq.ih.ptr_mask (cik_ih.c:205) β€” no 16-byte alignment validation.
  2. The consumer walk steps rptr strictly 16 bytes. amdgpu_ih_process (amdgpu_ih.c:154-157) loops while (ih->rptr != wptr); cik advances rptr by exactly 16 in both prescreen_iv (cik_ih.c:255) and decode_iv (cik_ih.c:287). A wptr with phase != 0 (mod 16) and no overflow bit can never equal rptr β†’ permanent ithread spin: 4096 IVs re-dispatched per lap of the 64 KiB ring, ih->lock held (amdgpu_ih.c:144), restart_ih re-reading the same wptr (amdgpu_ih.c:163-165). One CPU pegged, all GPU interrupt delivery dead. (Note: if the overflow bit IS set, the recovery transfers the wptr's phase into rptr, and after at most one full lap rptr does reach wptr β€” the unbounded spin specifically needs the no-overflow misaligned case, same as DF-4843.)
  3. OOB aggravator (DF-4854 shape, 12-byte variant, zero-slack BO). Overflow recovery (cik_ih.c:192-204) computes rptr = (wptr + 16) & 0xFFFF (cik_ih.c:200) from the unaligned wptr. With wptr' in [0xFFE4..0xFFEF] (phase 4..15, overflow set), rptr lands in [0xFFF4..0xFFFF]; prescreen_iv/decode_iv then read ring[ring_index .. ring_index+3], ring_index = rptr>>2 up to 0x3FFF (cik_ih.c:243-246, 274-277) β†’ dwords 0x4000..0x4002 = up to 12 bytes past the 64 KiB ring. The CIK ring is a amdgpu_bo_create_kernel GTT BO of exactly ring_size (amdgpu_ih.c:80-88) β€” no +8 slack (that slack exists only in the use_bus_addr coherent path, amdgpu_ih.c:61-68). The 12 OOB bytes are decoded as an IV, forwarded to amdgpu_amdkfd_interrupt (amdgpu_irq.c:170) and dispatched; dispatch indices are separately bounds-checked in amdgpu_irq.c, so the blast radius is the OOB read + a bogus dispatch (worst case: adjacent mapped page data influencing which handler runs, or a fault on unmapped vmalloc space β†’ panic).

Batch questions answered

  • wb vs direct reg: wb (cik_ih.c:190) β€” same as tonga; cik_ih never reads mmIH_RB_WPTR for polling.
  • same RB_OVERFLOW path: yes β€” cik_ih.c:192-204, +16 recovery like tonga (+32 on vega10's 32-byte grid).
  • doorbell: none for CIK IH β€” funcs table (cik_ih.c:462-467) has no doorbell member of interest; set_rptr uses mmIH_RB_RPTR (cik_ih.c:297-300).
  • prescreen/pasid/idr (cik_ih.c:238-257): same amdgpu_vm_pasid_fault_credit call as tonga (amdgpu_vm.c:3370-3397, idr_find under pasid_lock); the credit-drop path advances rptr by 16 β€” consistent with the walk; no cik-unique defect. !pasid β†’ process (kernel-context fault), matching tonga/upstream.
  • process-loop count cap: none in the shared walk (amdgpu_ih.c:154) β€” inherited by all ASICs; already covered by DF-4843's cap recommendation; fix.diff here keeps to this file's get_wptr plus the shared-loop cap.

Why Low / not uid0

The corrupted wptr must come from device DMA (hostile or faulting GPU / PCIe fault). No unprivileged local software path writes the wb slot. The OOB read is ≀ 12 bytes, read-only, and its content only steers existing IRQ handlers whose own indices are validated. Impact ceiling: permanent GPU-interrupt-thread spin (DoS) + tiny OOB read β†’ Low, consistent with DF-4843/DF-4854.

Fix

fix.diff: align-normalize the wptr after overflow-bit clear (wptr &= ~0xF with a dev_warn on mismatch, and derive the recovery rptr from the aligned value), plus a per-invocation entry cap in the shared walk so no bad wptr can hard-spin the ithread.

Fix validation

Not possible on this guest (no CIK hardware) β€” fix_status: not_testable; the diff is authored against the read-only sys/ tree and never applied there.

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.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

fix validation not possible: finding is device-gated (no CIK GPU on the audit guest); fix mirrors the DF-4843/DF-4854 recommendation (align + walk cap), static-only

fix.diff (git-apply-able vs read-only sys/ tree; never applied); VERDICT.md 'Fix' section
↓ fix.diffper-fix-DF-4887

Confirmed kernel references

Detail

Evidence (decisive lines)

README.md (batch questions answered + exact arithmetic with path:line); VERDICT.md (proof + fix rationale); trigger_sketch.c (hostile-device write pattern for both defects); fix.diff (align-normalize + shared-walk cap)

PoC changes

sketch only; no runnable PoC without CIK hardware (device-DMA wptr source)

Verified recommended fix

in cik_ih_get_wptr, align-normalize the wptr to the 16-byte IV boundary after clearing RB_OVERFLOW (warn on mismatch) and derive the overflow-recovery rptr from the aligned value; cap per-invocation entries in amdgpu_ih_process so a bad wptr or storm cannot hard-spin the interrupt thread

Verdict

Not runnable on the audit guest (no AMD GPU; the wptr source is device DMA into the host writeback slot, which software on the guest cannot produce). Code-certain by inspection - third instance of the just-filed DF-4843/DF-4854 family, confirmed SAME shape, not different: cik_ih_get_wptr reads the wb slot (cik_ih.c:190; use_bus_addr=false from cik_ih.c:321, so wb is the only path - CIK has NO IH doorbell, set_rptr uses mmIH_RB_RPTR at cik_ih.c:297-300) and returns it with nothing but &ptr_mask (cik_ih.c:205) - 16-byte alignment never validated. Core defect: amdgpu_ih_process (amdgpu_ih.c:154-157) walks with rptr stepping strictly 16 bytes (cik_ih.c:255 prescreen-drop, cik_ih.c:287 decode); a misaligned wptr without the overflow bit can never equal rptr - the amdgpu interrupt thread laps the 64KiB ring forever (4096 IVs re-dispatched/lap) with ih->lock held (amdgpu_ih.c:144) and restart_ih re-reading the same wptr (amdgpu_ih.c:163-165): one CPU pegged, all GPU IRQ delivery dead. Aggravator (DF-4854 shape, 12-byte variant): with RB_OVERFLOW set and overflow-cleared wptr in [0xFFE4..0xFFEF] (sub-16 phase 4..15 near ring end), the recovery rptr=(wptr+16)&0xFFFF (cik_ih.c:200) lands in [0xFFF4..0xFFFF], so the first prescreen/decode (cik_ih.c:243-246, 274-277) reads ring[ring_index..ring_index+3] with ring_index up to 0x3FFF -> dwords 0x4000..0x4002 = up to 12 bytes PAST the ring - and the CIK ring is an amdgpu_bo_create_kernel GTT BO of exactly ring_size (amdgpu_ih.c:80-88) with ZERO slack (the +8 slack only exists in the use_bus_addr path, amdgpu_ih.c:61-68), unlike vega10's 20B-past-a-slacked-allocation. OOB bytes are decoded as an IV, forwarded to amdgpu_amdkfd_interrupt (amdgpu_irq.c:170) and dispatched (dispatch indices separately bounds-checked). Prescreen/PASID (cik_ih.c:238-257) matches tonga: amdgpu_vm_pasid_fault_credit (amdgpu_vm.c:3370-3397) idr_find under pasid_lock, drop path advances rptr by 16 consistently - no cik-unique defect. Hostile/faulting-device model: DoS + <=12-byte read-only OOB read; no uid0 route; Low, consistent with the twins.