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)
PoC verification
Evidence pack
findings/poc/DF-2324 Β· 22 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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
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) loopswhile (pci_fixup_nextptr(&nextptr))wherenextptris 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) loopswhile (ptr != 0)withptr = 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_testableConfirmed 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.
No comments yet.