xhci: fix possible null pointer deref during xhci urb enqueue
authorMathias Nyman <mathias.nyman@linux.intel.com>
Fri, 1 Dec 2023 15:06:47 +0000 (17:06 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 4 Dec 2023 06:50:40 +0000 (07:50 +0100)
There is a short gap between urb being submitted and actually added to the
endpoint queue (linked). If the device is disconnected during this time
then usb core is not yet aware of the pending urb, and device may be freed
just before xhci_urq_enqueue() continues, dereferencing the freed device.

Freeing the device is protected by the xhci spinlock, so make sure we take
and keep the lock while checking that device exists, dereference it, and
add the urb to the queue.

Remove the unnecessary URB check, usb core checks it before calling
xhci_urb_enqueue()

Suggested-by: Kuen-Han Tsai <khtsai@google.com>
Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
Link: https://lore.kernel.org/r/20231201150647.1307406-20-mathias.nyman@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/host/xhci.c

index 71a117db21a7a76ba43bf5eca09e7eb254f4beb0..08fbabff23a0537e606e4ee3ced1f254d89920f7 100644 (file)
@@ -1521,24 +1521,7 @@ static int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag
        struct urb_priv *urb_priv;
        int num_tds;
 
-       if (!urb)
-               return -EINVAL;
-       ret = xhci_check_args(hcd, urb->dev, urb->ep,
-                                       true, true, __func__);
-       if (ret <= 0)
-               return ret ? ret : -EINVAL;
-
-       slot_id = urb->dev->slot_id;
        ep_index = xhci_get_endpoint_index(&urb->ep->desc);
-       ep_state = &xhci->devs[slot_id]->eps[ep_index].ep_state;
-
-       if (!HCD_HW_ACCESSIBLE(hcd))
-               return -ESHUTDOWN;
-
-       if (xhci->devs[slot_id]->flags & VDEV_PORT_ERROR) {
-               xhci_dbg(xhci, "Can't queue urb, port error, link inactive\n");
-               return -ENODEV;
-       }
 
        if (usb_endpoint_xfer_isoc(&urb->ep->desc))
                num_tds = urb->number_of_packets;
@@ -1562,12 +1545,35 @@ static int xhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flag
 
        spin_lock_irqsave(&xhci->lock, flags);
 
+       ret = xhci_check_args(hcd, urb->dev, urb->ep,
+                             true, true, __func__);
+       if (ret <= 0) {
+               ret = ret ? ret : -EINVAL;
+               goto free_priv;
+       }
+
+       slot_id = urb->dev->slot_id;
+
+       if (!HCD_HW_ACCESSIBLE(hcd)) {
+               ret = -ESHUTDOWN;
+               goto free_priv;
+       }
+
+       if (xhci->devs[slot_id]->flags & VDEV_PORT_ERROR) {
+               xhci_dbg(xhci, "Can't queue urb, port error, link inactive\n");
+               ret = -ENODEV;
+               goto free_priv;
+       }
+
        if (xhci->xhc_state & XHCI_STATE_DYING) {
                xhci_dbg(xhci, "Ep 0x%x: URB %p submitted for non-responsive xHCI host.\n",
                         urb->ep->desc.bEndpointAddress, urb);
                ret = -ESHUTDOWN;
                goto free_priv;
        }
+
+       ep_state = &xhci->devs[slot_id]->eps[ep_index].ep_state;
+
        if (*ep_state & (EP_GETTING_STREAMS | EP_GETTING_NO_STREAMS)) {
                xhci_warn(xhci, "WARN: Can't enqueue URB, ep in streams transition state %x\n",
                          *ep_state);