/*
 * DF-4158 supporting evidence: hostile-input exerciser for the vga.c ioctl
 * surface reachable through syscons (/dev/ttyvN) and (when compiled with
 * FB_INSTALL_CDEV) /dev/vgaN.
 *
 * Not a crasher: it asserts that every user-fed scalar (mode number, display
 * start, line width, palette index/count) is rejected or clamped by the
 * kernel instead of being used as an index/length.
 *
 * build: cc -O -o vgafuzz vgafuzz.c
 * run:   ./vgafuzz /dev/ttyv0
 */
#include <sys/ioctl.h>
#include <sys/fbio.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>

static int fd;
static int fails;

static void
expect_reject(const char *what, unsigned long cmd, void *arg)
{
	int rv = ioctl(fd, cmd, arg);
	if (rv == 0) {
		printf("FAIL %-38s : accepted (rv=0) -- needs review\n", what);
		fails++;
	} else {
		printf("ok   %-38s : rejected (%s)\n", what, strerror(errno));
	}
}

int
main(int argc, char **argv)
{
	const char *dev = argc > 1 ? argv[1] : "/dev/ttyv0";
	int i, mode;
	video_display_start_t start;
	video_color_palette_t pal;
	struct fbcmap cmap;
	video_info_t vinfo;
	unsigned char palbuf[768];

	fd = open(dev, O_RDWR);
	if (fd < 0) {
		printf("open %s: %s\n", dev, strerror(errno));
		return (1);
	}
	printf("opened %s\n", dev);

	/* --- (a) user-fed mode number as table index ------------------- */
	int modes[] = {INT_MIN, -1, -100, 29, 64, 256, 65535, 0x40000000,
		       INT_MAX};
	for (i = 0; i < (int)(sizeof(modes) / sizeof(modes[0])); i++) {
		mode = modes[i];
		char buf[64];
		snprintf(buf, sizeof(buf), "FBIO_SETMODE mode=%d", mode);
		expect_reject(buf, FBIO_SETMODE, &mode);
	}

	/* MODEINFO with hostile vi_mode (fb.c passes it as get_info index) */
	memset(&vinfo, 0, sizeof(vinfo));
	vinfo.vi_mode = INT_MAX;
	expect_reject("FBIO_MODEINFO vi_mode=INT_MAX", FBIO_MODEINFO, &vinfo);
	vinfo.vi_mode = -1;
	expect_reject("FBIO_MODEINFO vi_mode=-1", FBIO_MODEINFO, &vinfo);
	vinfo.vi_mode = 29;
	expect_reject("FBIO_MODEINFO vi_mode=29", FBIO_MODEINFO, &vinfo);

	/* sanity: current mode must be reported fine */
	if (ioctl(fd, FBIO_GETMODE, &mode) == 0)
		printf("ok   %-38s : %d\n", "FBIO_GETMODE (sanity)", mode);
	else {
		printf("note FBIO_GETMODE: %s\n", strerror(errno));
	}

	/* --- (b) display start / line width ----------------------------- */
	start.x = INT_MAX; start.y = INT_MAX;
	if (ioctl(fd, FBIO_SETDISPSTART, &start) == 0)
		printf("ok   %-38s : accepted (hw regs only)\n",
		       "FBIO_SETDISPSTART INT_MAX/INT_MAX");
	else
		printf("ok   %-38s : %s\n", "FBIO_SETDISPSTART INT_MAX",
		       strerror(errno));
	start.x = INT_MIN; start.y = INT_MIN;
	(void)ioctl(fd, FBIO_SETDISPSTART, &start);
	printf("ok   %-38s : survived\n", "FBIO_SETDISPSTART INT_MIN");

	i = INT_MAX;
	expect_reject("FBIO_SETLINEWIDTH INT_MAX", FBIO_SETLINEWIDTH, &i);
	i = 0;
	(void)ioctl(fd, FBIO_SETLINEWIDTH, &i);
	printf("ok   %-38s : survived\n", "FBIO_SETLINEWIDTH 0");
	i = -1;		/* becomes 0xffffffff via *(u_int *)arg */
	(void)ioctl(fd, FBIO_SETLINEWIDTH, &i);
	printf("ok   %-38s : survived\n", "FBIO_SETLINEWIDTH -1");

	/* --- (c) palette index/count arithmetic ------------------------- */
	memset(&pal, 0, sizeof(pal));
	pal.index = 0; pal.count = 256;			/* valid boundary */
	pal.red = palbuf; pal.green = palbuf + 256; pal.blue = palbuf + 512;
	if (ioctl(fd, FBIO_GETPALETTE, &pal) == 0)
		printf("ok   %-38s : index=0 count=256 served\n",
		       "FBIO_GETPALETTE boundary");
	else
		printf("note FBIO_GETPALETTE 0/256: %s\n", strerror(errno));

	pal.index = 255; pal.count = 2;			/* valid boundary */
	if (ioctl(fd, FBIO_GETPALETTE, &pal) == 0)
		printf("ok   %-38s : index=255 count=2 served\n",
		       "FBIO_GETPALETTE boundary");
	else
		printf("note FBIO_GETPALETTE 255/2: %s\n", strerror(errno));

	pal.index = 255; pal.count = 255;		/* base+count > 256 */
	expect_reject("FBIO_GETPALETTE 255/255", FBIO_GETPALETTE, &pal);
	pal.index = -1; pal.count = 1;
	expect_reject("FBIO_GETPALETTE index=-1", FBIO_GETPALETTE, &pal);
	pal.index = 0; pal.count = INT_MAX;
	expect_reject("FBIO_GETPALETTE count=INT_MAX", FBIO_GETPALETTE, &pal);
	pal.index = 0; pal.count = -1;
	expect_reject("FBIO_GETPALETTE count=-1", FBIO_GETPALETTE, &pal);
	pal.index = INT_MAX; pal.count = 1;
	expect_reject("FBIO_GETPALETTE index=INT_MAX", FBIO_GETPALETTE, &pal);

	/* copyin variant */
	pal.index = 0; pal.count = 0x40000000;
	expect_reject("FBIO_SETPALETTE count=2^30", FBIO_SETPALETTE, &pal);
	pal.index = 300; pal.count = 1;
	expect_reject("FBIO_SETPALETTE index=300", FBIO_SETPALETTE, &pal);

	memset(&cmap, 0, sizeof(cmap));
	cmap.index = 0; cmap.count = INT_MAX;
	cmap.red = palbuf; cmap.green = palbuf; cmap.blue = palbuf;
	expect_reject("FBIOGETCMAP count=INT_MAX", FBIOGETCMAP, &cmap);
	cmap.index = -5; cmap.count = 4;
	expect_reject("FBIOGETCMAP index=-5", FBIOGETCMAP, &cmap);
	cmap.index = 0; cmap.count = 0x7fffffff;
	expect_reject("FBIOPUTCMAP count=INT_MAX", FBIOPUTCMAP, &cmap);

	printf("%s: %d unexpected accept(s)\n", dev, fails);
	return (fails ? 2 : 0);
}
