DragonFlyBSD Kernel Audit
DF-2676 / df2676.c
← back to finding ↓ download raw
/*
 * DF-2676 trigger (kernel module): deterministically drive vm_page_grab()
 * into its "page exists but busy + caller did not pass VM_ALLOC_RETRY"
 * error path (sys/vm/vm_page.c:3838-3844) and prove the subsequent
 * NULL-pointer dereference at sys/vm/vm_page.c:3882 (m->valid).
 *
 * Two kernel threads:
 *   holder  - allocates nothing; just wakes the pre-busied page after 250ms
 *   grabber - calls vm_page_grab(obj, 0, VM_ALLOC_NORMAL)  (NO RETRY)
 *             -> lookup_busy_try error -> vm_page_sleep_busy (sleeps once,
 *                holder's wakeup releases it) -> m = NULL; break;
 *             -> "if (m->valid == 0)" dereferences NULL -> trap 12 panic.
 *
 * NOTE: with only in-tree callers this path is currently latent (the only
 * non-RETRY caller, sysv_shm's prealloc loop, holds the object exclusively
 * and can never observe a busy page); this module proves the primitive
 * exactly as any future non-RETRY caller would hit it.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/module.h>
#include <sys/thread.h>
#include <sys/kthread.h>
#include <vm/vm.h>
#include <vm/vm_object.h>
#include <vm/vm_page.h>
#include <vm/vm_page2.h>

static vm_object_t df_obj;
static vm_page_t df_m;
static struct thread *df_th0;
static struct thread *df_th1;
static struct thread *df_th2;

static void
df_holder(void *arg)
{
	tsleep(&df_m, 0, "df26h", hz / 4);
	kprintf("DF-2676: holder: waking busy page %p\n", df_m);
	vm_page_wakeup(df_m);
	kthread_exit();
}

static void
df_grabber(void *arg)
{
	vm_page_t m;

	tsleep(&df_m, 0, "df26g", hz / 8);
	kprintf("DF-2676: grabber: calling vm_page_grab(obj, 0, "
		"VM_ALLOC_NORMAL) - NO VM_ALLOC_RETRY - on busy page\n");
	m = vm_page_grab(df_obj, 0, VM_ALLOC_NORMAL);
	kprintf("DF-2676: grabber: grab returned %p (no panic?!)\n", m);
	kthread_exit();
}

static void
df_setup(void *arg)
{
	/*
	 * All object/token work must happen in kernel-thread context
	 * (never in the kldload syscall context, whose tokens are
	 * checked when returning to userland).
	 */
	df_obj = vm_object_allocate(OBJT_DEFAULT, 4);
	vm_object_hold(df_obj);
	df_m = vm_page_alloc(df_obj, 0, VM_ALLOC_NORMAL | VM_ALLOC_NULL_OK);
	KKASSERT(df_m != NULL);
	kprintf("DF-2676: loaded, page %p busied in obj %p\n", df_m, df_obj);
	kthread_create(df_holder, NULL, &df_th1, "df2676h%d", 0);
	kthread_create(df_grabber, NULL, &df_th2, "df2676g%d", 0);
	for (;;)
		tsleep(&df_obj, 0, "df2676w", hz * 60);
}

static int
df2676_event(struct module *mod, int what, void *arg)
{
	switch (what) {
	case MOD_LOAD:
		kthread_create(df_setup, NULL, &df_th0, "df2676s%d", 0);
		return (0);
	case MOD_UNLOAD:
		kprintf("DF-2676: unload (unreachable after panic)\n");
		return (0);
	default:
		return (0);
	}
}

static moduledata_t df2676_mod = {
	"df2676",
	df2676_event,
	NULL
};
DECLARE_MODULE(df2676, df2676_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
MODULE_VERSION(df2676, 1);