--- a/sys/bus/u4b/usb_generic.c +++ b/sys/bus/u4b/usb_generic.c @@ -75,6 +75,15 @@ /* function prototypes */ +/* + * Sentinel stored into f->fs_xfer[] while a USB_FS_OPEN setup is in + * flight (DF-5315): the slot is claimed under priv_lock before + * usbd_transfer_setup() is entered and is either published with the + * finished xfer pointer or cleared again. Concurrent USB_FS_OPEN / + * USB_FS_CLOSE / USB_FS_UNINIT must treat it like a busy slot. + */ +#define UGEN_FS_XFER_SETUP ((struct usb_xfer *)1) + static usb_callback_t ugen_read_clear_stall_callback; static usb_callback_t ugen_write_clear_stall_callback; static usb_callback_t ugen_ctrl_read_callback; @@ -604,8 +613,20 @@ static int ugen_set_config(struct usb_fifo *f, uint8_t index) { + int error; + DPRINTFN(2, "index %u\n", index); + /* + * DF-5316: changing the device configuration detaches drivers + * and re-enumerates the device; require the same privilege that + * ugen_check_request() demands for the raw UR_SET_CONFIG + * control request. + */ + error = caps_priv_check_self(SYSCAP_NODRIVER); + if (error) + return (error); + if (f->udev->flags.usb_mode != USB_MODE_HOST) { /* not possible in device side mode */ return (ENOTTY); @@ -628,8 +649,18 @@ ugen_set_interface(struct usb_fifo *f, uint8_t iface_index, uint8_t alt_index) { + int error; + DPRINTFN(2, "%u, %u\n", iface_index, alt_index); + /* + * DF-5316: same gate as ugen_check_request() applies to the raw + * UR_SET_INTERFACE control request. + */ + error = caps_priv_check_self(SYSCAP_NODRIVER); + if (error) + return (error); + if (f->udev->flags.usb_mode != USB_MODE_HOST) { /* not possible in device side mode */ return (ENOTTY); @@ -972,15 +1003,38 @@ int ugen_fs_uninit(struct usb_fifo *f) { + struct usb_xfer **fs_xfer; + uint8_t n; + uint8_t fs_ep_max; + + /* + * DF-5288/DF-5314: detach the endpoint array under priv_lock + * BEFORE freeing it, and refuse to uninitialize while a + * USB_FS_OPEN setup is still in flight on any slot. + */ + lockmgr(f->priv_lock, LK_EXCLUSIVE); + if (f->fs_xfer == NULL) { + lockmgr(f->priv_lock, LK_RELEASE); return (EINVAL); } - usbd_transfer_unsetup(f->fs_xfer, f->fs_ep_max); - kfree(f->fs_xfer, M_USB); + for (n = 0; n != f->fs_ep_max; n++) { + if (f->fs_xfer[n] == UGEN_FS_XFER_SETUP) { + lockmgr(f->priv_lock, LK_RELEASE); + return (EBUSY); + } + } + fs_xfer = f->fs_xfer; + fs_ep_max = f->fs_ep_max; f->fs_xfer = NULL; f->fs_ep_max = 0; f->fs_ep_ptr = NULL; f->flag_iscomplete = 0; + + lockmgr(f->priv_lock, LK_RELEASE); + + usbd_transfer_unsetup(fs_xfer, fs_ep_max); + kfree(fs_xfer, M_USB); usb_fifo_free_buffer(f); return (0); } @@ -1045,23 +1099,38 @@ int error; uint8_t isread; - if (ep_index >= f->fs_ep_max) { + /* + * DF-5288/DF-5314: acquire the FIFO lock, revalidate the array + * under it and keep it held across the whole function (it is a + * sleepable lockmgr lock), so that neither a concurrent + * USB_FS_UNINIT nor USB_FS_CLOSE can free "xfer" while we are + * installing transfer parameters into it. Every exit path + * below releases the lock exactly once. + */ + lockmgr(f->priv_lock, LK_EXCLUSIVE); + + if ((ep_index >= f->fs_ep_max) || (f->fs_xfer == NULL)) { + lockmgr(f->priv_lock, LK_RELEASE); return (EINVAL); } xfer = f->fs_xfer[ep_index]; if (xfer == NULL) { + lockmgr(f->priv_lock, LK_RELEASE); return (EINVAL); } - lockmgr(f->priv_lock, LK_EXCLUSIVE); + if (xfer == UGEN_FS_XFER_SETUP) { + lockmgr(f->priv_lock, LK_RELEASE); + return (EBUSY); /* setup in flight */ + } if (usbd_transfer_pending(xfer)) { lockmgr(f->priv_lock, LK_RELEASE); return (EBUSY); /* should not happen */ } - lockmgr(f->priv_lock, LK_RELEASE); error = copyin(f->fs_ep_ptr + ep_index, &fs_ep, sizeof(fs_ep)); if (error) { + lockmgr(f->priv_lock, LK_RELEASE); return (error); } /* security checks */ @@ -1089,6 +1158,7 @@ error = copyin(fs_ep.pLength, &length, sizeof(length)); if (error) { + lockmgr(f->priv_lock, LK_RELEASE); return (error); } if (length != sizeof(*req)) { @@ -1098,6 +1168,7 @@ if (length != 0) { error = copyin(uaddr, req, length); if (error) { + lockmgr(f->priv_lock, LK_RELEASE); return (error); } } @@ -1159,7 +1230,7 @@ error = copyin(fs_ep.pLength + n, &length, sizeof(length)); if (error) { - break; + goto done_locked; } usbd_xfer_set_frame_len(xfer, n, length); @@ -1175,7 +1246,7 @@ error = copyin(fs_ep.ppBuffer + n, &uaddr, sizeof(uaddr)); if (error) { - break; + goto done_locked; } if (xfer->flags_int.isochronous_xfr) { /* get kernel buffer address */ @@ -1192,15 +1263,17 @@ /* move data */ error = copyin(uaddr, kaddr, length); if (error) { - break; + goto done_locked; } } offset += length; } +done_locked: + lockmgr(f->priv_lock, LK_RELEASE); return (error); complete: - lockmgr(f->priv_lock, LK_EXCLUSIVE); + /* priv_lock is still held by the validation paths above */ ugen_fs_set_complete(f, ep_index); lockmgr(f->priv_lock, LK_RELEASE); return (0); @@ -1223,23 +1296,31 @@ int error; uint8_t isread; - if (ep_index >= f->fs_ep_max) - return (EINVAL); + /* DF-5288/DF-5314: see ugen_fs_copy_in() */ + lockmgr(f->priv_lock, LK_EXCLUSIVE); + if ((ep_index >= f->fs_ep_max) || (f->fs_xfer == NULL)) { + lockmgr(f->priv_lock, LK_RELEASE); + return (EINVAL); + } xfer = f->fs_xfer[ep_index]; - if (xfer == NULL) + if (xfer == NULL) { + lockmgr(f->priv_lock, LK_RELEASE); return (EINVAL); - - lockmgr(f->priv_lock, LK_EXCLUSIVE); + } + if (xfer == UGEN_FS_XFER_SETUP) { + lockmgr(f->priv_lock, LK_RELEASE); + return (EBUSY); /* setup in flight */ + } if (usbd_transfer_pending(xfer)) { lockmgr(f->priv_lock, LK_RELEASE); return (EBUSY); /* should not happen */ } - lockmgr(f->priv_lock, LK_RELEASE); fs_ep_uptr = f->fs_ep_ptr + ep_index; error = copyin(fs_ep_uptr, &fs_ep, sizeof(fs_ep)); if (error) { + lockmgr(f->priv_lock, LK_RELEASE); return (error); } fs_ep.status = xfer->error; @@ -1282,7 +1363,7 @@ error = copyin(fs_ep.pLength + n, &temp, sizeof(temp)); if (error) { - return (error); + goto done_locked; } if (temp > rem) { /* the userland length has been corrupted */ @@ -1308,7 +1389,7 @@ error = copyin(fs_ep.ppBuffer + n, &uaddr, sizeof(uaddr)); if (error) { - return (error); + goto done_locked; } if (xfer->flags_int.isochronous_xfr) { /* only one frame buffer */ @@ -1322,7 +1403,7 @@ /* move data */ error = copyout(kaddr, uaddr, length); if (error) { - return (error); + goto done_locked; } } /* @@ -1335,11 +1416,21 @@ error = copyout(&length, fs_ep.pLength + n, sizeof(length)); if (error) { - return (error); + goto done_locked; } } +done_locked: + lockmgr(f->priv_lock, LK_RELEASE); + complete: + /* + * DF-5314: paths that jump here directly from the validation + * section above still hold priv_lock; release it exactly once. + */ + if (lockowned(f->priv_lock)) + lockmgr(f->priv_lock, LK_RELEASE); + /* update "aFrames" */ error = copyout(&fs_ep.aFrames, &fs_ep_uptr->aFrames, sizeof(fs_ep.aFrames)); @@ -1426,18 +1517,34 @@ if (error) break; lockmgr(f->priv_lock, LK_EXCLUSIVE); + /* DF-5288/DF-5314: revalidate under the lock */ + if ((u.pstart->ep_index >= f->fs_ep_max) || + (f->fs_xfer == NULL)) { + lockmgr(f->priv_lock, LK_RELEASE); + error = EINVAL; + break; + } xfer = f->fs_xfer[u.pstart->ep_index]; usbd_transfer_start(xfer); lockmgr(f->priv_lock, LK_RELEASE); break; case USB_FS_STOP: - if (u.pstop->ep_index >= f->fs_ep_max) { + lockmgr(f->priv_lock, LK_EXCLUSIVE); + /* DF-5288/DF-5314: revalidate under the lock */ + if ((u.pstop->ep_index >= f->fs_ep_max) || + (f->fs_xfer == NULL)) { + lockmgr(f->priv_lock, LK_RELEASE); error = EINVAL; break; } - lockmgr(f->priv_lock, LK_EXCLUSIVE); - xfer = f->fs_xfer[u.pstart->ep_index]; + /* DF-5317: index the array with the checked union member */ + xfer = f->fs_xfer[u.pstop->ep_index]; + if (xfer == UGEN_FS_XFER_SETUP) { + lockmgr(f->priv_lock, LK_RELEASE); + error = EBUSY; + break; + } if (usbd_transfer_pending(xfer)) { usbd_transfer_stop(xfer); /* @@ -1459,10 +1566,14 @@ error = EINVAL; break; } - if (f->fs_xfer[u.popen->ep_index] != NULL) { + lockmgr(f->priv_lock, LK_EXCLUSIVE); + if ((f->fs_xfer == NULL) || + (f->fs_xfer[u.popen->ep_index] != NULL)) { + lockmgr(f->priv_lock, LK_RELEASE); error = EBUSY; break; } + lockmgr(f->priv_lock, LK_RELEASE); if (u.popen->max_bufsize > USB_FS_MAX_BUFSIZE) { u.popen->max_bufsize = USB_FS_MAX_BUFSIZE; } @@ -1535,36 +1646,86 @@ } } } + /* + * DF-5315: claim the slot with a sentinel under + * priv_lock BEFORE entering usbd_transfer_setup() + * (which sleeps and NULLs its target array entry at + * entry, usb_transfer.c:935). The setup runs into a + * local pointer; only the winner of the claim performs + * a setup, so no second setup can orphan the first + * one's usb_xfer (leaked M_USB block + leaked + * endpoint->refcount_alloc). + */ + lockmgr(f->priv_lock, LK_EXCLUSIVE); + if ((f->fs_xfer == NULL) || + (f->fs_xfer[u.popen->ep_index] != NULL)) { + lockmgr(f->priv_lock, LK_RELEASE); + error = EBUSY; + break; + } + f->fs_xfer[u.popen->ep_index] = UGEN_FS_XFER_SETUP; + lockmgr(f->priv_lock, LK_RELEASE); + error = usbd_transfer_setup(f->udev, &iface_index, - f->fs_xfer + u.popen->ep_index, usb_config, 1, - f, f->priv_lock); + &xfer, usb_config, 1, f, f->priv_lock); + if (error == 0) { + /* store index of endpoint */ + xfer->priv_fifo = ((uint8_t *)0) + u.popen->ep_index; + } + lockmgr(f->priv_lock, LK_EXCLUSIVE); + if ((f->fs_xfer != NULL) && + (f->fs_xfer[u.popen->ep_index] == UGEN_FS_XFER_SETUP)) { + /* publish the finished transfer, or clear the + * claim on failure */ + f->fs_xfer[u.popen->ep_index] = + (error == 0) ? xfer : NULL; + } + lockmgr(f->priv_lock, LK_RELEASE); if (error == 0) { /* update maximums */ - u.popen->max_packet_length = - f->fs_xfer[u.popen->ep_index]->max_frame_size; - u.popen->max_bufsize = - f->fs_xfer[u.popen->ep_index]->max_data_length; + u.popen->max_packet_length = xfer->max_frame_size; + u.popen->max_bufsize = xfer->max_data_length; /* update number of frames */ - u.popen->max_frames = - f->fs_xfer[u.popen->ep_index]->nframes; - /* store index of endpoint */ - f->fs_xfer[u.popen->ep_index]->priv_fifo = - ((uint8_t *)0) + u.popen->ep_index; + u.popen->max_frames = xfer->nframes; } else { error = ENOMEM; } break; case USB_FS_CLOSE: + /* + * DF-5314: clear the slot under priv_lock and unsetup + * the claimed pointer. usbd_transfer_unsetup() reads + * its array entry BEFORE taking the xfer lock + * (usb_transfer.c:1379), so two racing closes would + * both observe the non-NULL pointer and run a full + * double unsetup (setup_refcount underflow -> panic / + * endpoint lockout). + */ if (u.pclose->ep_index >= f->fs_ep_max) { error = EINVAL; break; } - if (f->fs_xfer[u.pclose->ep_index] == NULL) { + lockmgr(f->priv_lock, LK_EXCLUSIVE); + if (f->fs_xfer == NULL) { + lockmgr(f->priv_lock, LK_RELEASE); + error = EINVAL; + break; + } + xfer = f->fs_xfer[u.pclose->ep_index]; + if (xfer == UGEN_FS_XFER_SETUP) { + lockmgr(f->priv_lock, LK_RELEASE); + error = EBUSY; + break; + } + if (xfer == NULL) { + lockmgr(f->priv_lock, LK_RELEASE); error = EINVAL; break; } - usbd_transfer_unsetup(f->fs_xfer + u.pclose->ep_index, 1); + f->fs_xfer[u.pclose->ep_index] = NULL; + lockmgr(f->priv_lock, LK_RELEASE); + usbd_transfer_unsetup(&xfer, 1); break; case USB_FS_CLEAR_STALL_SYNC: @@ -1572,22 +1733,31 @@ error = EINVAL; break; } - if (f->fs_xfer[u.pstall->ep_index] == NULL) { + if (f->udev->flags.usb_mode != USB_MODE_HOST) { error = EINVAL; break; } - if (f->udev->flags.usb_mode != USB_MODE_HOST) { + lockmgr(f->priv_lock, LK_EXCLUSIVE); + if ((f->fs_xfer == NULL) || + (f->fs_xfer[u.pstall->ep_index] == NULL)) { + lockmgr(f->priv_lock, LK_RELEASE); error = EINVAL; break; } - lockmgr(f->priv_lock, LK_EXCLUSIVE); - error = usbd_transfer_pending(f->fs_xfer[u.pstall->ep_index]); + if (f->fs_xfer[u.pstall->ep_index] == UGEN_FS_XFER_SETUP) { + lockmgr(f->priv_lock, LK_RELEASE); + error = EBUSY; + break; + } + /* DF-5314: snapshot under the lock */ + xfer = f->fs_xfer[u.pstall->ep_index]; + error = usbd_transfer_pending(xfer); + ep = xfer->endpoint; lockmgr(f->priv_lock, LK_RELEASE); if (error) { return (EBUSY); } - ep = f->fs_xfer[u.pstall->ep_index]->endpoint; /* setup a clear-stall packet */ req.bmRequestType = UT_WRITE_ENDPOINT;