DragonFlyBSD Kernel Audit
DF-2556 / leak_mod.c
← back to finding ↓ download raw
/*
 * DF-2556 diagnostic module — directly exercises clist_alloc_cblocks() with
 * alternating sizes to confirm the c_data leak at the function level.
 *
 * On MOD_LOAD it runs N resize cycles on a private clist, each cycle calling
 * clist_alloc_cblocks(SIZE_A) then clist_alloc_cblocks(SIZE_B). With the bug,
 * each resize leaks the previous c_data M_TTYS buffer (never freed). We print
 * M_TTYS usage before and after via the kmem stats so growth is observable.
 *
 * Build: see build_mod.sh. Run (root): kldload ./leak_mod.ko
 */
#include <sys/param.h>
#include <sys/module.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/tty.h>

static MALLOC_DEFINE(M_DF2556T, "df2556t", "DF2556 leak test marker");

static int
leak_mod_load(module_t mod, int what, void *arg)
{
	struct clist cl;
	int i, n;
	const int sizeA = 1024;	/* shorts */
	const int sizeB = 16384;/* shorts; different bucket -> forces realloc */

	switch (what) {
	case MOD_LOAD:
		/* prime: allocate an initial buffer so the first resize has an old one */
		bzero(&cl, sizeof(cl));
		clist_alloc_cblocks(&cl, sizeA);

		n = 20000;
		kprintf("DF2556: running %d clist resize cycles (sizeA=%d sizeB=%d)\n",
			n, sizeA, sizeB);
		for (i = 0; i < n; i++) {
			/* alternate sizes; each change leaks the previous c_data */
			clist_alloc_cblocks(&cl, (i & 1) ? sizeB : sizeA);
		}
		kprintf("DF2556: done. On the buggy kernel ~%d M_TTYS buffers were "
			"leaked (never kfree'd); check 'vmstat -m | grep ttys'.\n", n);
		clist_free_cblocks(&cl);	/* frees only the last buffer */
		return 0;
	case MOD_UNLOAD:
		return 0;
	}
	return 0;
}

static moduledata_t leak_mod = {
	"df2556_leak",
	leak_mod_load,
	NULL
};
DECLARE_MODULE(df2556_leak, leak_mod, SI_SUB_PSEUDO, SI_ORDER_ANY);
/* clist_alloc_cblocks() is built into the kernel; no MODULE_DEPEND needed */