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

Capability list traversal lacks cycle detection - infinite loop DoS from malicious device with circular capability chain

Summary

Both capability-list walkers - pci_read_capabilities() (:877 during device enumeration) and pci_find_extcap_method() (:1407 called by drivers via pci_find_extcap) - loop on device-controlled next pointer values without cycle detection or iteration cap. Malicious PCIe device returning non-zero next pointer forming cycle (e.g. cap at 0x40 -> next=0x60 cap at 0x60 -> next=0x40) hangs kernel indefinitely in tight config-space read loop. pci_find_extcap_method worse: no alignment or minimum-offset sanitization that pci_fixup_nextptr provides. pci_find_extcap reachable from any driver calling pci_find_extcap(dev cap &ptr). System hangs during boot or hotplug enumeration. [VERIFIED pass2: reproduced - both walkers spin forever on 0x40-0x60 cycle, 12s hard-IRQ systimer watchdog marker panics captured for both; unaligned 0x45 0x4A pointer follow in extcap demonstrated; iteration-cap fix-validated - cycles return, legit chain digest byte-identical]

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2324 Β· 22 files
FileTypeDescriptionSize
README.md β€” 1.6 KB ↓ raw
VERDICT.md β€” 4.5 KB ↓ raw
build.log β€” 531 B view raw
env.txt β€” 2.9 KB view raw
manifest.json β€” 1.2 KB view raw
panic_cap_cycle.txt β€” 2.6 KB view raw
panic_extcap_cycle.txt β€” 2.6 KB view raw
rig/build.sh β€” 2.1 KB view raw
rig/cflags_evidence.txt β€” 236 B view raw
rig/check_verbatim.sh β€” 801 B view raw
rig/check_verbatim_fixed.sh β€” 801 B view raw
rig/dfpci_post.c β€” 25.2 KB view raw
rig/dfpci_pre.c β€” 8.8 KB view raw
rig/fix.diff β€” 4.4 KB view raw
rig/fixed_runs.log β€” 6.0 KB view raw
rig/gen.sh β€” 2.6 KB view raw
rig/gen_fixed.sh β€” 2.6 KB view raw
rig/linear_baseline.log β€” 427 B view raw
rig/mk_extract.py β€” 4.7 KB view raw
rig/run_2324.sh β€” 966 B view raw
run.log β€” 2.6 KB view raw
verdict.json β€” 3.3 KB view raw

DF-2324 β€” PCI capability-list traversal lacks cycle detection β€” evidence pack

Finding: sys/bus/pci/pci.c:877-892 (pci_read_capabilities) and pci.c:1407-1414 (pci_find_extcap_method) loop on device-controlled capability next-pointers with no iteration bound (and extcap follows unaligned pointers without the pci_fixup_nextptr masking) β€” a malicious device with a cyclic cap list hangs kernel enumeration.

Verdict (short)

REPRODUCED on the stock INVARIANTS kernel: both walkers driven with a fabricated 0x40<->0x60 cycle NEVER returned; a 2 Hz hard-IRQ systimer watchdog fired at 12 s with marker panics (panic_cap_cycle.txt, panic_extcap_cycle.txt) β€” decisive hang proof. The unaligned-pointer follow (0x40->0x45->0x4A) also demonstrated (extcap, no masking). Legit linear chains enumerate identically pre/post fix (CAPDIGEST byte-identical). Fixed kernel: both cycles terminate (RETURNED / ENXIO).

Rig

Shared with DF-2323 (same kernel-compiled verbatim-interposition harness, same verbatim sha256 gates β€” see DF-2323/rig and env.txt). Trigger: sysctl debug.dfpcih.cmd="cap cycle|cap linear|extcap cycle|extcap linear|extcap odd".

Reproduce

See rig/run_2324.sh; fixed-kernel A/B in rig/fixed_runs.log (DFPCIPF = production config + rig/fix.diff).

Files

  • VERDICT.md β€” narrative, tables, fix A/B
  • panic_cap_cycle.txt, panic_extcap_cycle.txt β€” watchdog hang captures
  • rig/linear_baseline.log β€” legit-chain digest (stock kernel)
  • rig/fixed_runs.log β€” fixed-kernel runs (cycles RETURN, digest identical)
  • rig/ β€” harness + generators + fix.diff (shared rig, identical copies)
  • env.txt, manifest.json, verdict.json
VERDICT.md
↓ download raw

DF-2324 β€” capability-list traversal has no cycle detection β€” VERDICT

status: reproduced / impact: panic (the mechanical capture of a kernel-hang / unbounded-enumeration-loop denial of service; the watchdog panic is the proof instrument, the observed behavior is that the walker never returns).

Line-accurate analysis (verified mechanically)

  • pci_read_capabilities (pci.c:877-892) loops while (pci_fixup_nextptr(&nextptr)) where nextptr is read from DEVICE config space (REG(ptr + PCICAP_NEXTPTR, 1) at pci.c:882). pci_fixup_nextptr (pci.c:601-634) only masks the low 2 bits and rejects values < 0x40 β€” it does NOT detect re-visits. A device that points 0x40 -> 0x60 -> 0x40 makes this loop spin forever.
  • pci_find_extcap_method (pci.c:1407-1414) loops while (ptr != 0) with ptr = pci_read_config(child, ptr + PCICAP_NEXTPTR, 1) β€” NO masking (unlike pci_fixup_nextptr), NO < 0x40 rejection, NO iteration bound. A self-loop or 2-cycle spins forever; unaligned pointers are followed verbatim.
  • Both walkers run during device enumeration (boot for every PCI device; hotplug/rescan later) and in driver probe/attach paths β€” a malicious device hangs the kernel in a tight loop with the bus lock context of the enumeration path (boot hang / livelocked CPU).

What was run

Fabricated config space driven through the VERBATIM walkers (sha256-gated extracts; see DF-2323 pack rig, shared).

run kernel result
cap linear (PMG->MSI->MSIX->EXPRESS->0) DFPCI (stock, INVARIANTS) enumerated fully; cfg digest logged (rig/linear_baseline.log): pp_cap=0xf001 msi_loc=0x50 msix_loc=0x60 msix_ctrl=0x3 msix_msgnum=4 msix_tbar=0x14 pba 0x14/0x10 expr_ptr=0x70 expr_cap=0x41 pcie=1
extcap linear DFPCI find MSI rc=0 capreg=0x50; find HT rc=2 (ENOENT)
extcap odd (next=0x45, 0x4A) DFPCI walker followed UNALIGNED pointers 0x40->0x45->0x4A->0 β€” no masking (the pci_fixup_nextptr contrast the finding asks about)
cap cycle (0x40<->0x60) DFPCI sysctl write NEVER returned; after ~12 s the hard-IRQ systimer watchdog fired: panic: DFPCI_WATCHDOG_HANG: 'cap cycle' stuck in cap-list loop (cycle, no iteration bound) β€” panic_cap_cycle.txt (systimer_intr <- pcpu_timer_process_oncpu hard-IRQ frames)
extcap cycle (0x40->0x60->0x40) DFPCI same hang, same watchdog capture β€” panic_extcap_cycle.txt
cap cycle DFPCIPF (production + fix.diff) RETURNED β€” === cap cycle RETURNED (loop terminated!) after the (0x100-0x40)/4 = 48-iteration bound
extcap cycle DFPCIPF RETURNED rc=6 (ENXIO) via the 64-iteration bound
cap linear DFPCIPF CAPDIGEST byte-IDENTICAL to the unfixed baseline (diff clean) β€” legit enumeration unchanged
extcap linear / extcap odd DFPCIPF find MSI rc=0 capreg=0x50, HT ENOENT unchanged; odd chain now masks (0x45->0x44) and terminates safely (rc=ENOENT)

The watchdog (rig/dfpci_post.c dfh_watchdog, 2 Hz systimer, fires from hard-interrupt context at 12 s) is the decisive-hang instrument: a 48-entry max walk returns in microseconds; a non-returning walker after 12 s of spinning is a hang by construction, and the panic backtrace shows the systimer interrupt frames.

Impact

Malicious PCI device (AV:P / crafted passthrough) hangs the kernel in an unbounded loop at enumeration time: boot never completes (every boot), hotplug rescan wedges, driver probe paths livelock. No memory corruption; availability only. If triggered post-boot the looping thread spins with preemption points absent (tight C loop) β€” on this UP-ish guest the console went unresponsive immediately.

Fix (validated A/B)

rig/fix.diff (combined diff, DF-2324 hunks): - pci_read_capabilities: int ncap guard, if (++ncap > (0x100 - 0x40) / 4) break; β€” config space has at most 48 aligned capability slots above 0x40. - pci_find_extcap_method: sanitize ptr &= ~0x3 (same masking as pci_fixup_nextptr, closing the unaligned-pointer follow) and if (++ncap > 0x100 / 4) return (ENXIO);.

A/B: cycles now terminate (RETURNED), the linear-chain digest is byte-identical pre/post fix, extcap find results unchanged.

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
per-fix-DF-2324

Confirmed kernel references

Detail

Exploit chain

malicious PCI device puts a cyclic next-pointer chain (0x40 next=0x60; 0x60 next=0x40) in config space -> pci_read_capabilities at boot enumeration spins forever -> boot hang; same cycle via pci_read_config in pci_find_extcap_method hangs driver probe/attach paths

Evidence (decisive lines)

panic_cap_cycle.txt (watchdog marker panic, systimer hard-IRQ frames, sysctl never returned), panic_extcap_cycle.txt (same for extcap), rig/linear_baseline.log (legit chain digest), rig/fixed_runs.log (cap cycle RETURNED, extcap cycle RETURNED rc=6, CAPDIGEST identical to baseline)

PoC changes

used the shared DFPCI verbatim-interposition harness (see DF-2323 pack): fabricated 256-byte config space served to the verbatim walkers through the PCIB_READ_CONFIG/pci_read_config interposition; watchdog is a periodic systimer whose callback panics from hard-interrupt context after 12 s if the armed command did not return

Verified recommended fix

add iteration caps to both walkers (48 aligned slots for read_capabilities, 64 for extcap) and apply pci_fixup_nextptr-style low-bit masking to pci_find_extcap_method

Verdict

Reproduced mechanically on the stock INVARIANTS kernel: the verbatim pci_read_capabilities loop (pci.c:877-892) never returned on a fabricated 0x40<->0x60 capability cycle (device-controlled next pointers; pci_fixup_nextptr masks alignment but does not detect revisits), and the verbatim pci_find_extcap_method loop (pci.c:1407-1414) never returned on the same cycle - both captured decisively by a 2 Hz hard-IRQ systimer watchdog that panics with a marker after 12 s (the sysctl caller was still stuck inside the walker). pci_find_extcap_method additionally follows UNALIGNED next pointers (0x40->0x45->0x4A observed), confirming the missing sanitization vs pci_fixup_nextptr. Impact is an unbounded-enumeration-loop hang: a malicious PCI device hangs boot/hotplug enumeration (availability only, no corruption); impact recorded as panic because the watchdog panic is the captured proof of the non-returning loop. Fix A/B validated: with the iteration caps (48 for read_capabilities, 64 for extcap) plus ptr masking in extcap, both cycles terminate (RETURNED / ENXIO) and the legit 4-cap linear chain digests byte-identically to the unfixed kernel.