DragonFlyBSD Kernel Audit
DF-0990 / fix.diff
← back to finding ↓ download raw
diff --git a/sys/bus/pci/pci.c b/sys/bus/pci/pci.c
index 0000000..1111111 100644
--- a/sys/bus/pci/pci.c
+++ b/sys/bus/pci/pci.c
@@ -873,21 +873,39 @@
 
 	/*
 	 * Read capability entries.
+	 *
+	 * Defensive bound: PCI capability pointers are 1-byte offsets into
+	 * config space, must be >= 0x40 and 4-byte aligned. The maximum
+	 * number of distinct valid pointers is (256 - 64) / 4 = 48. Cap the
+	 * loop iteration count to detect malicious or buggy devices with a
+	 * cyclic cap list (e.g. a self-loop cap@0x80 next=0x80) that would
+	 * otherwise spin here forever doing config reads at full CPU speed.
 	 */
-	while (pci_fixup_nextptr(&nextptr)) {
-		const struct pci_read_cap *rc;
-		int ptr = nextptr;
+	{
+		int cap_iter = 0;
+		while (pci_fixup_nextptr(&nextptr)) {
+			const struct pci_read_cap *rc;
+			int ptr = nextptr;
 
-		/* Find the next entry */
-		nextptr = REG(ptr + PCICAP_NEXTPTR, 1);
-
-		/* Process this entry */
-		val = REG(ptr + PCICAP_ID, 1);
-		for (rc = pci_read_caps; rc->read_cap != NULL; ++rc) {
-			if (rc->cap == val) {
-				rc->read_cap(pcib, ptr, nextptr, cfg);
+			if (++cap_iter > 64) {
+				kprintf("pci%d:%d:%d:%d: capability list walk "
+				    "exceeded %d entries (cyclic list?), aborting\n",
+				    cfg->domain, cfg->bus, cfg->slot, cfg->func,
+				    cap_iter - 1);
 				break;
 			}
+
+			/* Find the next entry */
+			nextptr = REG(ptr + PCICAP_NEXTPTR, 1);
+
+			/* Process this entry */
+			val = REG(ptr + PCICAP_ID, 1);
+			for (rc = pci_read_caps; rc->read_cap != NULL; ++rc) {
+				if (rc->cap == val) {
+					rc->read_cap(pcib, ptr, nextptr, cfg);
+					break;
+				}
+			}
 		}
 	}