From cd9a4fdd89670aeee7b1aaec9a8acdf7fe2efd37 Mon Sep 17 00:00:00 2001 From: Taylor R Campbell <riastradh@NetBSD.org> Date: Sat, 12 Jun 2021 16:47:58 +0000 Subject: [PATCH 1/3] usb(4): Tighten interface locking and pipe references. - Just use a reference count, not a list of pipes. - Take the reference in usbd_open_pipe*, before we even look up the endpoint by address; the endpoint is not stable until we hold the interface and prevent usbd_set_interface. - Make opening pipes just fail if usbd_set_interface is in progress. => No need to block -- might block for a while, and this is essentially a driver error rather than a legitimate reason to block. => This should maybe be a kassert, but it's not clear that ugen(4) doesn't have a user-triggerable path to that kassert, so let's keep it as a graceful failure for now until someone can audit ugen(4) and make an informed decision. - No need for a separate interface pipe lock; just use the bus lock. This is a little bit longer than before, but makes the bracketed nature of the references a little clearer and introduces more kasserts to detect mistakes with internal API usage. --- sys/dev/usb/usb_subr.c | 142 ++++++++++++++++++++++++++++++++++------- sys/dev/usb/usbdi.c | 61 +++++++++++++----- sys/dev/usb/usbdivar.h | 9 ++- 3 files changed, 169 insertions(+), 43 deletions(-) diff --git a/sys/dev/usb/usb_subr.c b/sys/dev/usb/usb_subr.c index c92c05163ac3..ed414cb2fa71 100644 --- a/sys/dev/usb/usb_subr.c +++ b/sys/dev/usb/usb_subr.c @@ -415,8 +415,7 @@ usbd_iface_init(struct usbd_device *dev, int ifaceidx) ifc->ui_index = 0; ifc->ui_altindex = 0; ifc->ui_endpoints = NULL; - LIST_INIT(&ifc->ui_pipes); - mutex_init(&ifc->ui_pipelock, MUTEX_DEFAULT, IPL_NONE); + ifc->ui_busy = 0; } static void @@ -429,9 +428,100 @@ usbd_iface_fini(struct usbd_device *dev, int ifaceidx) KASSERT(ifc->ui_index == 0); KASSERT(ifc->ui_altindex == 0); KASSERT(ifc->ui_endpoints == NULL); - KASSERT(LIST_EMPTY(&ifc->ui_pipes)); + KASSERTMSG(ifc->ui_busy == 0, "%"PRId64, ifc->ui_busy); +} + +/* + * usbd_iface_lock/locked/unlock, usbd_iface_piperef/pipeunref + * + * We lock the interface while we are setting it, and we acquire a + * reference to the interface for each pipe opened on it. + * + * Setting the interface while pipes are open is forbidden, and + * opening pipes while the interface is being set is forbidden. + */ + +bool +usbd_iface_locked(struct usbd_interface *iface) +{ + bool locked; + + mutex_enter(iface->ui_dev->ud_bus->ub_lock); + locked = (iface->ui_busy == -1); + mutex_exit(iface->ui_dev->ud_bus->ub_lock); + + return locked; +} + +static void +usbd_iface_exlock(struct usbd_interface *iface) +{ + + mutex_enter(iface->ui_dev->ud_bus->ub_lock); + KASSERTMSG(iface->ui_busy == 0, "interface is not idle," + " busy=%"PRId64, iface->ui_busy); + iface->ui_busy = -1; + mutex_exit(iface->ui_dev->ud_bus->ub_lock); +} + +usbd_status +usbd_iface_lock(struct usbd_interface *iface) +{ + usbd_status err; + + mutex_enter(iface->ui_dev->ud_bus->ub_lock); + KASSERTMSG(iface->ui_busy != -1, "interface is locked"); + KASSERTMSG(iface->ui_busy >= 0, "%"PRId64, iface->ui_busy); + if (iface->ui_busy) { + err = USBD_IN_USE; + } else { + iface->ui_busy = -1; + err = 0; + } + mutex_exit(iface->ui_dev->ud_bus->ub_lock); + + return err; +} + +void +usbd_iface_unlock(struct usbd_interface *iface) +{ + + mutex_enter(iface->ui_dev->ud_bus->ub_lock); + KASSERTMSG(iface->ui_busy == -1, "interface is not locked," + " busy=%"PRId64, iface->ui_busy); + iface->ui_busy = 0; + mutex_exit(iface->ui_dev->ud_bus->ub_lock); +} + +usbd_status +usbd_iface_piperef(struct usbd_interface *iface) +{ + usbd_status err; + + mutex_enter(iface->ui_dev->ud_bus->ub_lock); + KASSERTMSG(iface->ui_busy >= -1, "%"PRId64, iface->ui_busy); + if (iface->ui_busy == -1) { + err = USBD_IN_USE; + } else { + iface->ui_busy++; + err = 0; + } + mutex_exit(iface->ui_dev->ud_bus->ub_lock); + + return err; +} - mutex_destroy(&ifc->ui_pipelock); +void +usbd_iface_pipeunref(struct usbd_interface *iface) +{ + + mutex_enter(iface->ui_dev->ud_bus->ub_lock); + KASSERTMSG(iface->ui_busy != -1, "interface is locked"); + KASSERTMSG(iface->ui_busy != 0, "interface not in use"); + KASSERTMSG(iface->ui_busy >= 1, "%"PRId64, iface->ui_busy); + iface->ui_busy--; + mutex_exit(iface->ui_dev->ud_bus->ub_lock); } usbd_status @@ -447,7 +537,7 @@ usbd_fill_iface_data(struct usbd_device *dev, int ifaceidx, int altidx) int endpt, nendpt; KASSERT(ifc->ui_dev == dev); - KASSERT(LIST_EMPTY(&ifc->ui_pipes)); + KASSERT(usbd_iface_locked(ifc)); idesc = usbd_find_idesc(dev->ud_cdesc, ifaceidx, altidx); if (idesc == NULL) @@ -547,7 +637,7 @@ usbd_free_iface_data(struct usbd_device *dev, int ifcno) KASSERT(ifc->ui_dev == dev); KASSERT(ifc->ui_idesc != NULL); - KASSERT(LIST_EMPTY(&ifc->ui_pipes)); + KASSERT(usbd_iface_locked(ifc)); if (ifc->ui_endpoints) { int nendpt = ifc->ui_idesc->bNumEndpoints; @@ -608,7 +698,9 @@ usbd_set_config_index(struct usbd_device *dev, int index, int msg) /* Free all configuration data structures. */ nifc = dev->ud_cdesc->bNumInterface; for (ifcidx = 0; ifcidx < nifc; ifcidx++) { + usbd_iface_exlock(&dev->ud_ifaces[ifcidx]); usbd_free_iface_data(dev, ifcidx); + usbd_iface_unlock(&dev->ud_ifaces[ifcidx]); usbd_iface_fini(dev, ifcidx); } kmem_free(dev->ud_ifaces, nifc * sizeof(struct usbd_interface)); @@ -783,10 +875,14 @@ usbd_set_config_index(struct usbd_device *dev, int index, int msg) dev->ud_config = cdp->bConfigurationValue; for (ifcidx = 0; ifcidx < nifc; ifcidx++) { usbd_iface_init(dev, ifcidx); + usbd_iface_exlock(&dev->ud_ifaces[ifcidx]); err = usbd_fill_iface_data(dev, ifcidx, 0); + usbd_iface_unlock(&dev->ud_ifaces[ifcidx]); if (err) { while (--ifcidx >= 0) { + usbd_iface_exlock(&dev->ud_ifaces[ifcidx]); usbd_free_iface_data(dev, ifcidx); + usbd_iface_unlock(&dev->ud_ifaces[ifcidx]); usbd_iface_fini(dev, ifcidx); } kmem_free(dev->ud_ifaces, @@ -827,12 +923,15 @@ usbd_setup_pipe_flags(struct usbd_device *dev, struct usbd_interface *iface, USBHIST_FUNC(); USBHIST_CALLARGS(usbdebug, "dev=%#jx addr=%jd iface=%#jx ep=%#jx", (uintptr_t)dev, dev->ud_addr, (uintptr_t)iface, (uintptr_t)ep); - struct usbd_pipe *p; + struct usbd_pipe *p = NULL; + bool ep_acquired = false; usbd_status err; + /* Block exclusive use of the endpoint by later pipes. */ err = usbd_endpoint_acquire(dev, ep, flags & USBD_EXCLUSIVE_USE); if (err) - return err; + goto out; + ep_acquired = true; p = kmem_alloc(dev->ud_bus->ub_pipesize, KM_SLEEP); DPRINTFN(1, "pipe=%#jx", (uintptr_t)p, 0, 0, 0); @@ -848,24 +947,11 @@ usbd_setup_pipe_flags(struct usbd_device *dev, struct usbd_interface *iface, p->up_flags = flags; SIMPLEQ_INIT(&p->up_queue); - if (iface) { - mutex_enter(&iface->ui_pipelock); - LIST_INSERT_HEAD(&iface->ui_pipes, p, up_next); - mutex_exit(&iface->ui_pipelock); - } - err = dev->ud_bus->ub_methods->ubm_open(p); if (err) { DPRINTF("endpoint=%#jx failed, error=%jd", (uintptr_t)ep->ue_edesc->bEndpointAddress, err, 0, 0); - if (iface) { - mutex_enter(&iface->ui_pipelock); - LIST_REMOVE(p, up_next); - mutex_exit(&iface->ui_pipelock); - } - kmem_free(p, dev->ud_bus->ub_pipesize); - usbd_endpoint_release(dev, ep); - return err; + goto out; } KASSERT(p->up_methods->upm_start || p->up_serialise == false); @@ -874,7 +960,15 @@ usbd_setup_pipe_flags(struct usbd_device *dev, struct usbd_interface *iface, USB_TASKQ_MPSAFE); DPRINTFN(1, "pipe=%#jx", (uintptr_t)p, 0, 0, 0); *pipe = p; - return USBD_NORMAL_COMPLETION; + p = NULL; /* handed off to caller */ + ep_acquired = false; /* handed off to pipe */ + err = USBD_NORMAL_COMPLETION; + +out: if (p) + kmem_free(p, dev->ud_bus->ub_pipesize); + if (ep_acquired) + usbd_endpoint_release(dev, ep); + return err; } usbd_status @@ -1712,7 +1806,9 @@ usb_free_device(struct usbd_device *dev) if (dev->ud_ifaces != NULL) { nifc = dev->ud_cdesc->bNumInterface; for (ifcidx = 0; ifcidx < nifc; ifcidx++) { + usbd_iface_exlock(&dev->ud_ifaces[ifcidx]); usbd_free_iface_data(dev, ifcidx); + usbd_iface_unlock(&dev->ud_ifaces[ifcidx]); usbd_iface_fini(dev, ifcidx); } kmem_free(dev->ud_ifaces, diff --git a/sys/dev/usb/usbdi.c b/sys/dev/usb/usbdi.c index ddb5e811a1d0..089957498ee5 100644 --- a/sys/dev/usb/usbdi.c +++ b/sys/dev/usb/usbdi.c @@ -225,6 +225,7 @@ usbd_open_pipe_ival(struct usbd_interface *iface, uint8_t address, { struct usbd_pipe *p; struct usbd_endpoint *ep; + bool piperef = false; usbd_status err; int i; @@ -232,22 +233,49 @@ usbd_open_pipe_ival(struct usbd_interface *iface, uint8_t address, USBHIST_CALLARGS(usbdebug, "iface = %#jx address = %#jx flags = %#jx", (uintptr_t)iface, address, flags, 0); + /* + * Block usbd_set_interface so we have a snapshot of the + * interface endpoints. They will remain stable until we drop + * the reference in usbd_close_pipe (or on failure here). + */ + err = usbd_iface_piperef(iface); + if (err) + goto out; + piperef = true; + + /* Find the endpoint at this address. */ for (i = 0; i < iface->ui_idesc->bNumEndpoints; i++) { ep = &iface->ui_endpoints[i]; - if (ep->ue_edesc == NULL) - return USBD_IOERROR; + if (ep->ue_edesc == NULL) { + err = USBD_IOERROR; + goto out; + } if (ep->ue_edesc->bEndpointAddress == address) - goto found; + break; } - return USBD_BAD_ADDRESS; - found: + if (i == iface->ui_idesc->bNumEndpoints) { + err = USBD_BAD_ADDRESS; + goto out; + } + + /* Set up the pipe with this endpoint. */ err = usbd_setup_pipe_flags(iface->ui_dev, iface, ep, ival, &p, flags); if (err) - return err; + goto out; + + /* Success! */ *pipe = p; + p = NULL; /* handed off to caller */ + piperef = false; /* handed off to pipe */ SDT_PROBE5(usb, device, pipe, open, iface, address, flags, ival, p); - return USBD_NORMAL_COMPLETION; + err = USBD_NORMAL_COMPLETION; + +out: if (p) + usbd_close_pipe(p); + if (piperef) + usbd_iface_pipeunref(iface); + return err; } usbd_status @@ -316,12 +344,9 @@ usbd_close_pipe(struct usbd_pipe *pipe) usbd_destroy_xfer(pipe->up_intrxfer); usb_rem_task_wait(pipe->up_dev, &pipe->up_async_task, USB_TASKQ_DRIVER, NULL); - if (pipe->up_iface) { - mutex_enter(&pipe->up_iface->ui_pipelock); - LIST_REMOVE(pipe, up_next); - mutex_exit(&pipe->up_iface->ui_pipelock); - } usbd_endpoint_release(pipe->up_dev, pipe->up_endpoint); + if (pipe->up_iface) + usbd_iface_pipeunref(pipe->up_iface); kmem_free(pipe, pipe->up_dev->ud_bus->ub_pipesize); return USBD_NORMAL_COMPLETION; @@ -865,17 +890,17 @@ usbd_pipe2device_handle(struct usbd_pipe *pipe) usbd_status usbd_set_interface(struct usbd_interface *iface, int altidx) { + bool locked = false; usb_device_request_t req; usbd_status err; USBHIST_FUNC(); USBHIST_CALLARGS(usbdebug, "iface %#jx", (uintptr_t)iface, 0, 0, 0); - mutex_enter(&iface->ui_pipelock); - if (LIST_FIRST(&iface->ui_pipes) != NULL) { - err = USBD_IN_USE; + err = usbd_iface_lock(iface); + if (err) goto out; - } + locked = true; err = usbd_fill_iface_data(iface->ui_dev, iface->ui_index, altidx); if (err) @@ -888,7 +913,9 @@ usbd_set_interface(struct usbd_interface *iface, int altidx) USETW(req.wLength, 0); err = usbd_do_request(iface->ui_dev, &req, 0); -out: mutex_exit(&iface->ui_pipelock); +out: /* XXX back out iface data? */ + if (locked) + usbd_iface_unlock(iface); return err; } diff --git a/sys/dev/usb/usbdivar.h b/sys/dev/usb/usbdivar.h index c275f6a2327f..6f036860990a 100644 --- a/sys/dev/usb/usbdivar.h +++ b/sys/dev/usb/usbdivar.h @@ -230,8 +230,7 @@ struct usbd_interface { int ui_index; int ui_altindex; struct usbd_endpoint *ui_endpoints; - kmutex_t ui_pipelock; - LIST_HEAD(, usbd_pipe) ui_pipes; + int64_t ui_busy; /* #pipes, or -1 if setting */ }; struct usbd_pipe { @@ -243,7 +242,6 @@ struct usbd_pipe { bool up_serialise; SIMPLEQ_HEAD(, usbd_xfer) up_queue; - LIST_ENTRY(usbd_pipe) up_next; struct usb_task up_async_task; struct usbd_xfer *up_intrxfer; /* used for repeating requests */ @@ -347,6 +345,11 @@ usbd_status usbd_reattach_device(device_t, struct usbd_device *, int, const int *); void usbd_remove_device(struct usbd_device *, struct usbd_port *); +bool usbd_iface_locked(struct usbd_interface *); +usbd_status usbd_iface_lock(struct usbd_interface *); +void usbd_iface_unlock(struct usbd_interface *); +usbd_status usbd_iface_piperef(struct usbd_interface *); +void usbd_iface_pipeunref(struct usbd_interface *); usbd_status usbd_fill_iface_data(struct usbd_device *, int, int); void usb_free_device(struct usbd_device *); From b65a497cacc9a1284b31c3ea09747b954ac9746a Mon Sep 17 00:00:00 2001 From: Taylor R Campbell <riastradh@NetBSD.org> Date: Sat, 12 Jun 2021 22:39:49 +0000 Subject: [PATCH 2/3] uhub(4): Trigger bus exploration after rescanning children. Otherwise, if uhub4 is attached at uhub1, then when we do # drvctl -d uhub4 # drvctl -r -a usbdevif uhub1 the rescan never discovers devices attached recursively at uhub4, and uhub4 leaks a config_pending_incr count so it can't be detached. --- sys/dev/usb/uhub.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sys/dev/usb/uhub.c b/sys/dev/usb/uhub.c index 84b85d1f5254..ec7e64dd1e58 100644 --- a/sys/dev/usb/uhub.c +++ b/sys/dev/usb/uhub.c @@ -946,6 +946,10 @@ uhub_rescan(device_t self, const char *ifattr, const int *locators) usbd_reattach_device(sc->sc_dev, dev, port, locators); } uhub_explore_exit(sc); + + /* Arrange to recursively explore hubs we may have found. */ + usb_needs_explore(sc->sc_hub); + return 0; } From 71736cb0628d9bd188825a520665e75c733eae6c Mon Sep 17 00:00:00 2001 From: Taylor R Campbell <riastradh@NetBSD.org> Date: Sat, 12 Jun 2021 22:41:22 +0000 Subject: [PATCH 3/3] autoconf(9): Sprinkle KASSERT(dev->dv_pending == 0) in dealloc paths. This would have made uhub's config_pending_incr leak more obvious by crashing in KASSERT(dev->dv_pending == 0) early on, rather than crashing in a tailq panic later on when the config_pending list gets corrupted with use-after-free because nothing took the device off dv_pending_list when attached. (This is slightly academic now because config_detach blocks until dev->dv_pending == 0, but it doesn't hurt and makes the intent clearer.) --- sys/kern/subr_autoconf.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sys/kern/subr_autoconf.c b/sys/kern/subr_autoconf.c index 4523d285b5c4..cd3701131ae9 100644 --- a/sys/kern/subr_autoconf.c +++ b/sys/kern/subr_autoconf.c @@ -1405,7 +1405,9 @@ config_devlink(device_t dev) static void config_devfree(device_t dev) { + KASSERT(dev->dv_flags & DVF_PRIV_ALLOC); + KASSERTMSG(dev->dv_pending == 0, "%d", dev->dv_pending); if (dev->dv_cfattach->ca_devsize > 0) kmem_free(dev->dv_private, dev->dv_cfattach->ca_devsize); @@ -1423,6 +1425,7 @@ config_devunlink(device_t dev, struct devicelist *garbage) int i; KASSERT(mutex_owned(&alldevs_lock)); + KASSERTMSG(dev->dv_pending == 0, "%d", dev->dv_pending); /* Unlink from device list. Link to garbage list. */ TAILQ_REMOVE(&alldevs, dev, dv_list); @@ -1453,6 +1456,8 @@ config_devdelete(device_t dev) struct device_garbage *dg = &dev->dv_garbage; device_lock_t dvl = device_getlock(dev); + KASSERTMSG(dev->dv_pending == 0, "%d", dev->dv_pending); + if (dg->dg_devs != NULL) kmem_free(dg->dg_devs, sizeof(device_t) * dg->dg_ndevs);