⬢ DragonFlyBSD Kernel Audit
DF-5308 / fix.diff
← back to finding ↓ download raw
--- a/sys/bus/u4b/usb_dev.c
+++ b/sys/bus/u4b/usb_dev.c
@@ -111,6 +111,8 @@
 static void usb_filter_detach(struct knote *kn);
 static int usb_filter_read(struct knote *kn, long hint);
 static int usb_filter_write(struct knote *kn, long hint);
+static void usb_filter_detach_unlink(struct usb_cdev_privdata *,
+		    struct knote *);
 
 static d_open_t usb_open;
 static d_close_t usb_close;
@@ -1238,6 +1240,17 @@
 
 	err = usb_ref_device(cpd, &refs, 0 /* no uref */ );
 	if (err) {
+		/*
+		 * The reference could not be taken (device detaching,
+		 * FIFO delinked, or the FIFO refcount is saturated),
+		 * but this knote is being destroyed right now and it
+		 * may still be linked into a live FIFO's klist.  If we
+		 * return without unlinking it, the next KNOTE() on
+		 * that FIFO walks freed memory and dispatches
+		 * kn->kn_fop->f_event through attacker-reclaimed
+		 * heap (use-after-free, DF-5308).
+		 */
+		usb_filter_detach_unlink(cpd, kn);
 		return;
 	}
 
@@ -1268,6 +1281,55 @@
 	usb_unref_device(cpd, &refs);
 }
 
+/*
+ * Best-effort unlink of a knote whose usb_ref_device() failed: re-derive
+ * the FIFO pointer under usb_ref_lock (the same lookup usb_ref_device()
+ * performs) and remove the knote from the FIFO's klist.  If the bus, the
+ * device or the FIFO slot is already gone, the klist died with the FIFO
+ * and there is nothing to unlink.
+ */
+static void
+usb_filter_detach_unlink(struct usb_cdev_privdata *cpd, struct knote *kn)
+{
+	struct usb_bus *bus;
+	struct usb_device *udev;
+	struct usb_fifo *f;
+	struct klist *klist;
+
+	lockmgr(&usb_ref_lock, LK_EXCLUSIVE);
+	bus = devclass_get_softc(usb_devclass_ptr, cpd->bus_index);
+	if (bus == NULL) {
+		lockmgr(&usb_ref_lock, LK_RELEASE);
+		return;
+	}
+	udev = bus->devices[cpd->dev_index];
+	if (udev == NULL) {
+		lockmgr(&usb_ref_lock, LK_RELEASE);
+		return;
+	}
+	switch (kn->kn_filter) {
+	case EVFILT_READ:
+		f = udev->fifo[cpd->fifo_index + USB_FIFO_RX];
+		break;
+	case EVFILT_WRITE:
+		f = udev->fifo[cpd->fifo_index + USB_FIFO_TX];
+		break;
+	default:
+		lockmgr(&usb_ref_lock, LK_RELEASE);
+		return;
+	}
+	lockmgr(&usb_ref_lock, LK_RELEASE);
+
+	if (f == NULL)
+		return;			/* FIFO is gone; klist died with it */
+
+	lockmgr(f->priv_lock, LK_EXCLUSIVE);
+	klist = &f->selinfo.ki_note;
+	knote_remove(klist, kn);
+	f->flag_isselect = 0;
+	lockmgr(f->priv_lock, LK_RELEASE);
+}
+
 static int
 usb_filter_read(struct knote *kn, long hint)
 {