USB: dummy-hcd: fix infinite-loop resubmission bug
authorAlan Stern <stern@rowland.harvard.edu>
Tue, 26 Sep 2017 19:15:40 +0000 (15:15 -0400)
committerFelipe Balbi <felipe.balbi@linux.intel.com>
Thu, 28 Sep 2017 09:30:56 +0000 (12:30 +0300)
The dummy-hcd HCD/UDC emulator tries not to do too much work during
each timer interrupt.  But it doesn't try very hard; currently all
it does is limit the total amount of bulk data transferred.  Other
transfer types aren't limited, and URBs that transfer no data (because
of an error, perhaps) don't count toward the limit, even though on a
real USB bus they would consume at least a minimum overhead.

This means it's possible to get the driver stuck in an infinite loop,
for example, if the host class driver resubmits an URB every time it
completes (which is common for interrupt URBs).  Each time the URB is
resubmitted it gets added to the end of the pending-URBs list, and
dummy-hcd doesn't stop until that list is empty.  Andrey Konovalov was
able to trigger this failure mode using the syzkaller fuzzer.

This patch fixes the infinite-loop problem by restricting the URBs
handled during each timer interrupt to those that were already on the
pending list when the interrupt routine started.  Newly added URBs
won't be processed until the next timer interrupt.  The problem of
properly accounting for non-bulk bandwidth (as well as packet and
transaction overhead) is not addressed here.

Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Reported-by: Andrey Konovalov <andreyknvl@google.com>
Tested-by: Andrey Konovalov <andreyknvl@google.com>
CC: <stable@vger.kernel.org>
Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com>
drivers/usb/gadget/udc/dummy_hcd.c

index d515ec31afe446205bf43232c3dfe93f6b5246c3..b2ab9cc33fec4be0e4a4ff7d804b547b0c00eade 100644 (file)
@@ -237,6 +237,8 @@ struct dummy_hcd {
 
        struct usb_device               *udev;
        struct list_head                urbp_list;
+       struct urbp                     *next_frame_urbp;
+
        u32                             stream_en_ep;
        u8                              num_stream[30 / 2];
 
@@ -1250,6 +1252,8 @@ static int dummy_urb_enqueue(
 
        list_add_tail(&urbp->urbp_list, &dum_hcd->urbp_list);
        urb->hcpriv = urbp;
+       if (!dum_hcd->next_frame_urbp)
+               dum_hcd->next_frame_urbp = urbp;
        if (usb_pipetype(urb->pipe) == PIPE_CONTROL)
                urb->error_count = 1;           /* mark as a new urb */
 
@@ -1766,6 +1770,7 @@ static void dummy_timer(unsigned long _dum_hcd)
                spin_unlock_irqrestore(&dum->lock, flags);
                return;
        }
+       dum_hcd->next_frame_urbp = NULL;
 
        for (i = 0; i < DUMMY_ENDPOINTS; i++) {
                if (!ep_info[i].name)
@@ -1782,6 +1787,10 @@ restart:
                int                     type;
                int                     status = -EINPROGRESS;
 
+               /* stop when we reach URBs queued after the timer interrupt */
+               if (urbp == dum_hcd->next_frame_urbp)
+                       break;
+
                urb = urbp->urb;
                if (urb->unlinked)
                        goto return_urb;