DF-2866 / pgtmark.c
/* * 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); |