DF-5308 / fix.diff
--- 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) { |