DragonFlyBSD Kernel Audit
DF-2915 / sqprobe.c
← back to finding ↓ download raw
/*
 * sqprobe.c - DF-2915 root-cause probe (safe on the stock kernel).
 *
 * Proves that objcache_create_simple()-backed caches return NON-ZEROED
 * objects, which is the false assumption behind the KKASSERT at
 * sys/kern/subr_sleepqueue.c:209:
 *
 *	wc = objcache_get(sleepq_wc_cache, M_WAITOK);
 *	KKASSERT(wc->wc_wchan == NULL && wc->wc_refs == 0);
 *
 * sleepq_wc_cache is created by objcache_create_simple(M_SLEEPQ, 48)
 * (subr_sleepqueue.c:145-146).  objcache_create_simple() wires up
 * objcache_malloc_alloc as the allocator with NO ctor
 * (sys/kern/kern_objcache.c:386-389), and objcache_malloc_alloc()
 * kmalloc()s without M_ZERO (kern_objcache.c:571-577).
 *
 * Method: spray 48-byte M_SLEEPQ objects filled with 0xAA, free them,
 * then build a cache exactly like sleepq_wc_cache does and pull objects.
 * Fresh/recycled chunks come back 0xAA-filled -> the KKASSERT condition
 * is false -> panic on INVARIANTS kernels; garbage wc enters
 * sc_wchead on production kernels.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/objcache.h>
#include <sys/module.h>

extern struct malloc_type M_SLEEPQ[1];	/* subr_sleepqueue.c:107 */

#define WC_SIZE		48		/* sizeof(struct sleepqueue_wchan) */
#define SPRAY		8192
#define PROBE		256

/* mirror of subr_sleepqueue.c:97-104 */
struct demo_wc {
	void	*tqe_next;		/* TAILQ_ENTRY wc_entry */
	void	**tqe_prev;
	const void *wc_wchan;		/* +16, checked by KKASSERT :209 */
	void	*wc_sc;			/* +24 */
	u_int	wc_refs;		/* +28, checked by KKASSERT :209 */
	int	wc_type;		/* +32 */
	u_int	wc_blocked[2];		/* +36 */
};

static __inline int
wc_bad(const struct demo_wc *w)
{
	return (w->wc_wchan != NULL || w->wc_refs != 0 ||
		w->wc_blocked[0] != 0 || w->wc_blocked[1] != 0);
}

static int
sqprobe_run(void)
{
	void **m;
	void *objs[PROBE];
	struct objcache *oc;
	int i, bad = 0;

	m = kmalloc(SPRAY * sizeof(void *), M_TEMP, M_WAITOK | M_ZERO);
	for (i = 0; i < SPRAY; ++i) {
		m[i] = kmalloc(WC_SIZE, M_SLEEPQ, M_WAITOK);
		memset(m[i], 0xAA, WC_SIZE);
	}
	for (i = 0; i < SPRAY; ++i)
		kfree(m[i], M_SLEEPQ);

	/* exactly how subr_sleepqueue.c:145-146 creates sleepq_wc_cache */
	oc = objcache_create_simple(M_SLEEPQ, WC_SIZE);

	for (i = 0; i < PROBE; ++i) {
		objs[i] = objcache_get(oc, M_WAITOK);
		if (wc_bad(objs[i]))
			++bad;
	}
	kprintf("SQDEMO probe: %d/%d objcache_get() objects violate the "
		"KKASSERT precondition at subr_sleepqueue.c:209 "
		"(uninitialized wc_wchan/wc_refs/wc_blocked)\n", bad, PROBE);
	for (i = 0; i < PROBE; ++i)
		objcache_put(oc, objs[i]);
	kfree(m, M_TEMP);

	return (bad > 0);
}

static int
sqprobe_handler(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		if (sqprobe_run())
			kprintf("SQDEMO probe: ROOT CAUSE OF DF-2915 "
				"REPRODUCED on stock kernel\n");
		else
			kprintf("SQDEMO probe: objects came back zeroed "
				"(unlucky slab) - rerun\n");
		break;
	default:
		break;
	}
	return 0;
}

static moduledata_t sqprobe_mod = { "sqprobe", sqprobe_handler, NULL };
DECLARE_MODULE(sqprobe, sqprobe_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);