DF-5011 / drainpoc.c
/* * DF-5011 PoC — dimm_mod_unload() drains references it does not own. * * drainpoc behaves exactly like sys/dev/misc/ecc/ecc_e3.c: it calls the * dimm(4) KPI (dimm_create/dimm_destroy) but does NOT declare * MODULE_DEPEND(<self>, dimm, ...). ecc_e3.c:156-157 only declares * MODULE_DEPEND on pci and coremctl. * * What this proves: * 1. `kldunload dimm` SUCCEEDS while drainpoc holds a live dimm_softc * reference — dimm_mod_unload() (dimm.c:312-318) loops * `dimm_destroy(TAILQ_FIRST(...))` until the list is empty, silently * draining drainpoc's reference and kfree()ing the softc. * 2. Using the softc afterwards (dimm_destroy at MOD_UNLOAD, or an ECC * interrupt in the real ecc_e3 case) is a use-after-free into module * memory that the linker has already unmapped. * * Build: WANT_DEP=0 (default) -> drainpoc.ko (no MODULE_DEPEND, the bug path) * WANT_DEP=1 -> deppoc.ko (control: with MODULE_DEPEND, * kldunload dimm must fail EBUSY) */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <dev/misc/dimm/dimm.h> static struct dimm_softc *poc_sc; static int drainpoc_load(void) { poc_sc = dimm_create(0, 0, 0); kprintf("drainpoc: dimm_create(0,0,0) -> sc=%p ref=1 held by drainpoc\n", poc_sc); return 0; } static int drainpoc_unload(void) { kprintf("drainpoc: MOD_UNLOAD: dimm_destroy(%p) on object freed by " "dimm_mod_unload's drain (use-after-free / call into unmapped " "module text)\n", poc_sc); dimm_destroy(poc_sc); kprintf("drainpoc: dimm_destroy returned without panic\n"); return 0; } static int drainpoc_event(module_t mod, int type, void *unused) { switch (type) { case MOD_LOAD: return drainpoc_load(); case MOD_UNLOAD: return drainpoc_unload(); default: return 0; } } #ifndef POCMODNAME #define POCMODNAME drainpoc #endif static moduledata_t drainpoc_mod = { __STRING(POCMODNAME), drainpoc_event, 0 }; DECLARE_MODULE(POCMODNAME, drainpoc_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); #ifdef WANT_DEP MODULE_DEPEND(POCMODNAME, dimm, 1, 1, 1); #endif |