/*
 * pgtmark.c -- KLD harness proving the pagertab[] / OBJT_MARKER size
 * mismatch in sys/vm/vm_pager.c.
 *
 * vm_pager.c:152-160 defines pagertab[] with SEVEN entries
 * (OBJT_DEFAULT=0 .. OBJT_DEAD=6), but enum obj_type
 * (sys/vm/vm_object.h:118-127) has EIGHT values -- OBJT_MARKER == 7.
 * Every dispatcher indexes without a bounds check:
 *
 *      vm_pager_deallocate()  sys/vm/vm_pager.c:340
 *      vm_pager_get_page()    sys/vm/vm_pager.h:134
 *      vm_pager_put_pages()   sys/vm/vm_pager.h:150
 *      vm_pager_has_page()    sys/vm/vm_pager.h:168
 *
 * so an object with type == OBJT_MARKER dispatched to any pager op reads
 * one `struct pagerops *` PAST the end of pagertab and calls through
 * whatever value sits there (pgo_haspage at +24).
 *
 * Stage 1 (always, at kldload): print pagertab[0..7].  Index 7 is the
 *          out-of-bounds slot -- whatever .data the linker placed after
 *          pagertab.  Reading the slot itself is harmless (mapped .data);
 *          the printout documents that it is not a pagerops.
 *
 * Stage 2 (sysctl vm.pgtmark.fire=1): construct a stack vm_object with
 *          type = OBJT_MARKER and call vm_pager_has_page() on it --
 *          exactly what any kernel path handing a marker to the pager
 *          dispatch would do.  Stock kernel: uncontrolled indirect call /
 *          page fault -> panic.  Fixed kernel (OBJT_MARKER slot mapped to
 *          &deadpagerops): returns FALSE, prints "NO PANIC".
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/sysctl.h>
#include <sys/types.h>

#include <vm/vm.h>
#include <vm/vm_param.h>
#include <vm/vm_object.h>
#include <vm/vm_page.h>
#include <vm/vm_pager.h>

static int
pgtmark_fire_sysctl(SYSCTL_HANDLER_ARGS)
{
	int error, val = 0;

	error = sysctl_handle_int(oidp, &val, 0, req);
	if (error || req->newptr == NULL)
		return (error);
	if (val == 1) {
		struct vm_object marker;
		boolean_t r;

		bzero(&marker, sizeof(marker));
		marker.type = OBJT_MARKER;	/* == 7, one past pagertab[] */

		kprintf("PGTMARK: dispatching OBJT_MARKER(%d) object via "
			"vm_pager_has_page(); pagertab[7] = %p\n",
			(int)marker.type, pagertab[7]);
		r = vm_pager_has_page(&marker, 0);
		kprintf("PGTMARK: vm_pager_has_page returned %d -- NO PANIC "
			"(fixed kernel)\n", (int)r);
	}
	return (0);
}
SYSCTL_PROC(_vm, OID_AUTO, pgtmark_fire,
    CTLTYPE_INT | CTLFLAG_RW, NULL, 0, pgtmark_fire_sysctl, "I",
    "write 1 to dispatch an OBJT_MARKER object through vm_pager_has_page()");

static int
pgtmark_modevent(module_t mod __unused, int type, void *data __unused)
{
	switch (type) {
	case MOD_LOAD: {
		int i;

		kprintf("PGTMARK: &pagertab=%p OBJT_MARKER=%d "
			"(pagertab has 7 legal slots 0..6)\n",
			&pagertab[0], (int)OBJT_MARKER);
		for (i = 0; i <= 7; ++i)
			kprintf("PGTMARK: pagertab[%d] = %p%s\n", i,
				pagertab[i], i == 7 ? "   <-- OOB SLOT" : "");
		break;
	}
	case MOD_UNLOAD:
		break;
	default:
		return (EOPNOTSUPP);
	}
	return (0);
}

static moduledata_t pgtmark_mod = { "pgtmark", pgtmark_modevent, NULL };
DECLARE_MODULE(pgtmark, pgtmark_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
MODULE_VERSION(pgtmark, 1);
