⬢ DragonFlyBSD Kernel Audit
DF-5287 / fix.diff
← back to finding ↓ download raw
--- a/sys/bus/u4b/usb_dev.h
+++ b/sys/bus/u4b/usb_dev.h
@@ -124,6 +124,7 @@
 	uint16_t dev_ep_index;		/* our device endpoint index */
 	uint8_t	flag_sleeping;		/* set if FIFO is sleeping */
 	uint8_t	flag_iscomplete;	/* set if a USB transfer is complete */
+	uint8_t	flag_setup_busy;	/* a ugen_transfer_setup() is in progress */
 	uint8_t	flag_iserror;		/* set if FIFO error happened */
 	uint8_t	flag_isselect;		/* set if FIFO is selected */
 	uint8_t	flag_flushing;		/* set if FIFO is flushing data */
--- a/sys/bus/u4b/usb_generic.c
+++ b/sys/bus/u4b/usb_generic.c
@@ -142,6 +142,14 @@
 	uint8_t iface_index = ep->iface_index;
 	int error;
 
+	KKASSERT(lockowned(f->priv_lock));
+
+	/* only one setup in progress at a time (DF-5287) */
+	if (f->flag_setup_busy) {
+		return (0);		/* loser of the race: nothing to do */
+	}
+	f->flag_setup_busy = 1;
+
 	lockmgr(f->priv_lock, LK_RELEASE);
 
 	/*
@@ -151,6 +159,14 @@
 	error = usbd_transfer_setup(udev, &iface_index, f->xfer,
 	    setup, n_setup, f, f->priv_lock);
 	if (error == 0) {
+		/*
+		 * Swap the FIFO buffers under priv_lock (DF-5287): the
+		 * previous code called usb_fifo_alloc_buffer() ->
+		 * usb_fifo_free_buffer() with no lock held, freeing the
+		 * fifo mbuf storage while live callbacks iterated the
+		 * queues.
+		 */
+		lockmgr(f->priv_lock, LK_EXCLUSIVE);
 
 		if (f->xfer[0]->nframes == 1) {
 			error = usb_fifo_alloc_buffer(f,
@@ -160,11 +176,21 @@
 			    f->xfer[0]->max_frame_size,
 			    2 * f->xfer[0]->nframes);
 		}
+
+		f->flag_setup_busy = 0;
+
 		if (error) {
+			/* "usbd_transfer_unsetup()" sleeps and takes the
+			 * lock itself -- drop it around the call, then
+			 * restore the caller's "locked on return" state. */
+			lockmgr(f->priv_lock, LK_RELEASE);
 			usbd_transfer_unsetup(f->xfer, n_setup);
+			lockmgr(f->priv_lock, LK_EXCLUSIVE);
 		}
+		return (error);
 	}
 	lockmgr(f->priv_lock, LK_EXCLUSIVE);
+	f->flag_setup_busy = 0;
 
 	return (error);
 }