⬢ DragonFlyBSD Kernel Audit
DF-5268 / fix.diff
← back to finding ↓ download raw
--- a/sys/bus/u4b/controller/xhci.c
+++ b/sys/bus/u4b/controller/xhci.c
@@ -628,6 +628,31 @@
 	sc->sc_runt_off = XREAD4(sc, capa, XHCI_RTSOFF) & ~0x1F;
 	sc->sc_door_off = XREAD4(sc, capa, XHCI_DBOFF) & ~0x3;
 
+	/*
+	 * DF-5268: The register offsets above are latched verbatim from
+	 * device-controlled MMIO. If a malicious (or firmware-compromised)
+	 * xHCI controller returns oversized CAPLENGTH/RTSOFF/DBOFF values,
+	 * every subsequent XREAD/XWRITE computes bus-space addresses far
+	 * outside the mapped BAR (out-of-bounds MMIO read/write: kernel
+	 * page fault, or cross-device register corruption). Validate them
+	 * against the size of the mapped register space before use.
+	 *
+	 * Bounds: the operational region needs the port status array
+	 * (0x400 + 0x10*nports), the runtime region at least one
+	 * interrupter block, and the doorbell array 4 bytes per slot
+	 * (max 256 slots).
+	 */
+	if (sc->sc_oper_off < 0x20 ||
+	    (uint64_t)sc->sc_oper_off + 0x400 > sc->sc_io_size ||
+	    (uint64_t)sc->sc_runt_off + 0x40 > sc->sc_io_size ||
+	    (uint64_t)sc->sc_door_off + 0x400 > sc->sc_io_size) {
+		device_printf(self, "Invalid register offsets "
+		    "(oper=0x%x runt=0x%x door=0x%x io_size=0x%llx)\n",
+		    sc->sc_oper_off, sc->sc_runt_off, sc->sc_door_off,
+		    (unsigned long long)sc->sc_io_size);
+		return (ENXIO);
+	}
+
 	DPRINTF("CAPLENGTH=0x%x\n", sc->sc_oper_off);
 	DPRINTF("RUNTIMEOFFSET=0x%x\n", sc->sc_runt_off);
 	DPRINTF("DOOROFFSET=0x%x\n", sc->sc_door_off);
@@ -1232,6 +1257,11 @@
 	sc->sc_command_idx = i;
 	sc->sc_command_ccs = j;
 
+	if ((uint64_t)sc->sc_door_off + 4 > sc->sc_io_size) {
+		/* doorbell outside the mapped register space */
+		USB_BUS_UNLOCK(&sc->sc_bus);
+		return (USB_ERR_IOERROR);
+	}
 	XWRITE4(sc, door, XHCI_DOORBELL(0), 0);
 
 	err = cv_timedwait(&sc->sc_cmd_cv, &sc->sc_bus.bus_lock,
@@ -2866,6 +2896,11 @@
 	index = xfer->xroot->udev->controller_slot_id;
 
 	if (xfer->xroot->udev->flags.self_suspended == 0) {
+		if ((uint64_t)sc->sc_door_off + 4 * ((uint32_t)index + 1) >
+		    sc->sc_io_size) {
+			/* doorbell outside the mapped register space */
+			return;
+		}
 		XWRITE4(sc, door, XHCI_DOORBELL(index),
 		    epno | XHCI_DB_SID_SET(xfer->stream_id));
 	}