/*
 * gfree2.c -- DF-5541 production-code harness.
 *
 * Demonstrates on the STOCK kernel that gpio_intel_free_intr()
 * (gpio_intel.c:250-263) performs no real validation of the cookie before
 * dispatching to the backend: the only guard is
 *     KKASSERT(gpio_intel_pin_exists(sc, map->pin));
 * which is compiled out on production (non-INVARIANTS) kernels, and which
 * would anyway pass for any in-range pin.  A stale / double-freed cookie
 * (map->pin == -1 after the first unmap_intr) therefore reaches
 * gpio_cherryview_unmap_intr() (gpio_cherryview.c:380-414), whose
 *     uint16_t pin = map->pin;
 * converts -1 to 65535, so the register restore does
 *     chvgpio_read/sc->write(PIN_CTL1(65535)) = PIN_CTL0(65535)
 *     = resource KVA + 0x448800/0x448804   (~4.5MB past the 0x8000-sized
 *                                           pmap_mapdev() mapping)
 * -> out-of-bounds kernel virtual memory READ plus conditional WRITE
 *    (bits 0..2 of the CTL1 word, bits 8..10 of the CTL0 word cleared).
 *
 * Runs on the DF-5518 planted-ACPI rig (fake INT33FF _UID 2 community,
 * RAM register image at 0xD0000+0x8000); stock drivers, no kernel
 * modifications.
 *
 * A/B experiment (single kldload):
 *   CONTROL: ALLOC -> SETUP -> TEARDOWN -> GPIO_FREE_INTR   (documented
 *            order; first free is in-range and clean)
 *   DEFECT : GPIO_FREE_INTR again on the same (now stale) cookie
 *            -> unmap_intr with pin == -1 -> uint16_t 65535 -> OOB
 *               KVA access 0x448800/0x448804 past the BAR mapping
 * Expected: control passes silently; the second free either panics
 *           (Fatal trap 12 at gpio_cherryview_unmap_intr, unmapped KVA)
 *           or silently reads AND potentially writes kernel memory
 *           4.5MB past the mapping (reported here if we survive).
 */
#include <sys/param.h>
#include <sys/module.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/rman.h>
#include <machine/vmparam.h>

#include <vm/vm_page.h>

#include "gpio_if.h"
#include "gpio_intel_var.h"	/* copied next to this file by build.sh */

/* Cherry-View register image offsets (gpio_cherryview.c:56-81) */
#define GPO_BASE	0x000D0000ul	/* _CRS Memory32Fixed window */
#define CHV_PINS	0x4400
#define PIN_CTL0(p)	(GPO_BASE + CHV_PINS + ((p) / 15) * 0x400 + \
			 ((p) % 15) * 8)

/* gpio_cherryview.c bit fields */
#define CTL0_GPIOEN	0x00008000u
#define INTSEL_SHIFT	28

/* acrestyp.h */
#define TRIG_EDGE	1	/* ACPI_EDGE_SENSITIVE */
#define POL_HIGH	0	/* ACPI_ACTIVE_HIGH */
#define TERM_DEFAULT	0	/* ACPI_PIN_CONFIG_DEFAULT */

#define PIN_A	8	/* valid chv_n pin, family 0 member 8 */
#define LINE_A	5

/* OOB target of pin == -1 (uint16_t 65535) via PIN_ADDRESS()/PIN_CTLx() */
#define OOB_CTL0_OFF	((65535 / 15) * 0x400 + (65535 % 15) * 8 + 0x4400)
#define OOB_CTL1_OFF	(OOB_CTL0_OFF + 4)

static uint32_t
rd32(vm_paddr_t pa)
{
	return (*(volatile uint32_t *)PHYS_TO_DMAP(pa));
}

static void
wr32(vm_paddr_t pa, uint32_t v)
{
	*(volatile uint32_t *)PHYS_TO_DMAP(pa) = v;
}

static void
dummy_intr(void *arg)
{
}

static int
gfree2_modevent(module_t mod, int type, void *arg)
{
	devclass_t dc;
	device_t dev;
	struct gpio_intel_softc *sc;
	void *cookie = NULL;
	int error;

	switch (type) {
	case MOD_LOAD:
		dc = devclass_find("gpio_intel");
		dev = dc ? devclass_get_device(dc, 0) : NULL;
		if (dev == NULL) {
			kprintf("gfree2: gpio_intel0 not found "
			    "(is the DF-5518 rig booted?)\n");
			return (ENXIO);
		}
		sc = device_get_softc(dev);
		kprintf("gfree2: gpio_intel0 dev=%p sc=%p mem_res=%p "
		    "KVA=%#jx size=%#lx\n", dev, sc, sc->mem_res,
		    (uintmax_t)(uintptr_t)rman_get_virtual(sc->mem_res),
		    (unsigned long)rman_get_size(sc->mem_res));
		kprintf("gfree2: stale-cookie OOB target KVA+%#x (CTL0) / "
		    "KVA+%#x (CTL1)  [pin -1 -> uint16_t 65535]\n",
		    OOB_CTL0_OFF, OOB_CTL1_OFF);

		/*
		 * Plant pad PIN_A in the register image:
		 *   CTL0 = INTSEL<<28 | GPIOEN  (GPIOCFG = 0: RX-capable)
		 *   CTL1 = INTCFG 2 (edge/rising, matches TRIG_EDGE/POL_HIGH)
		 */
		wr32(PIN_CTL0(PIN_A), (LINE_A << INTSEL_SHIFT) | CTL0_GPIOEN);
		wr32(PIN_CTL0(PIN_A) + 4, 2u);
		kprintf("gfree2: planted pin%d CTL0=%08x CTL1=%08x\n",
		    PIN_A, rd32(PIN_CTL0(PIN_A)), rd32(PIN_CTL0(PIN_A) + 4));
		kprintf("gfree2: map5 before: pin=%d intidx=%d\n",
		    sc->intrmaps[LINE_A].pin, sc->intrmaps[LINE_A].intidx);

		/* ---- CONTROL: ALLOC -> SETUP -> TEARDOWN -> FREE ---- */
		error = GPIO_ALLOC_INTR(dev, PIN_A, TRIG_EDGE, POL_HIGH,
		    TERM_DEFAULT, &cookie);
		kprintf("gfree2[CONTROL]: GPIO_ALLOC_INTR(pin%d line%d) = %d "
		    "cookie=%p\n", PIN_A, LINE_A, error, cookie);
		if (error != 0)
			break;
		GPIO_SETUP_INTR(dev, cookie, NULL, dummy_intr);
		GPIO_TEARDOWN_INTR(dev, cookie);
		GPIO_FREE_INTR(dev, cookie);
		kprintf("gfree2[CONTROL]: documented order done; "
		    "map5 now: pin=%d intidx=%d (freed cleanly)\n",
		    sc->intrmaps[LINE_A].pin, sc->intrmaps[LINE_A].intidx);

		/* ---- DEFECT: FREE the SAME (stale) cookie again ---- */
		kprintf("gfree2[DEFECT]: calling GPIO_FREE_INTR on the stale "
		    "cookie again (map->pin == %d)...\n",
		    sc->intrmaps[LINE_A].pin);
		GPIO_FREE_INTR(dev, cookie);
		/*
		 * If we get here the OOB KVA read (and conditional write)
		 * at KVA+0x448800/0x448804 happened silently.
		 */
		kprintf("gfree2[DEFECT]: SURVIVED -- OOB access at KVA+%#x / "
		    "KVA+%#x was silent; kernel memory past the 32KB mapdev "
		    "mapping was read (and bits 0..2 / 8..10 conditionally "
		    "cleared) by the stale-cookie unmap\n",
		    OOB_CTL0_OFF, OOB_CTL1_OFF);
		kprintf("gfree2: guest still running; check dmesg for "
		    "corruption side-effects\n");
		break;
	case MOD_UNLOAD:
		kprintf("gfree2: unloaded\n");
		break;
	default:
		return (EOPNOTSUPP);
	}
	return (0);
}

static moduledata_t gfree2_mod = { "gfree2", gfree2_modevent, NULL };
DECLARE_MODULE(gfree2, gfree2_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
MODULE_VERSION(gfree2, 1);
