diff options
author | Thomas Pugliese <thomas.pugliese@gmail.com> | 2013-10-01 10:14:56 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2013-10-03 15:46:26 -0700 |
commit | 33186c441684de348636f94412d2fc256e641113 (patch) | |
tree | df8348447bd8abb82f60fc564ed78809d5f8fe10 /drivers/usb/wusbcore | |
parent | 02c123ee99c793f65af2dbda17d5fe87d448f808 (diff) | |
download | talos-obmc-linux-33186c441684de348636f94412d2fc256e641113.tar.gz talos-obmc-linux-33186c441684de348636f94412d2fc256e641113.zip |
usb: wusbcore: avoid stack overflow in URB enqueue error path
This patch modifies wa_urb_enqueue to return an error and not call the
urb completion routine if it failed to enqueue the urb because the HWA
device is gone. This prevents a stack overflow due to infinite
submit/complete recursion when unplugging the HWA while connected to a
HID device.
Signed-off-by: Thomas Pugliese <thomas.pugliese@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/usb/wusbcore')
-rw-r--r-- | drivers/usb/wusbcore/wa-xfer.c | 51 |
1 files changed, 38 insertions, 13 deletions
diff --git a/drivers/usb/wusbcore/wa-xfer.c b/drivers/usb/wusbcore/wa-xfer.c index 9dabd8957d60..13faac0ea99f 100644 --- a/drivers/usb/wusbcore/wa-xfer.c +++ b/drivers/usb/wusbcore/wa-xfer.c @@ -1052,7 +1052,7 @@ error_seg_submit: * result never kicks in, the xfer will timeout from the USB code and * dequeue() will be called. */ -static void wa_urb_enqueue_b(struct wa_xfer *xfer) +static int wa_urb_enqueue_b(struct wa_xfer *xfer) { int result; unsigned long flags; @@ -1063,18 +1063,22 @@ static void wa_urb_enqueue_b(struct wa_xfer *xfer) unsigned done; result = rpipe_get_by_ep(wa, xfer->ep, urb, xfer->gfp); - if (result < 0) + if (result < 0) { + pr_err("%s: error_rpipe_get\n", __func__); goto error_rpipe_get; + } result = -ENODEV; /* FIXME: segmentation broken -- kills DWA */ mutex_lock(&wusbhc->mutex); /* get a WUSB dev */ if (urb->dev == NULL) { mutex_unlock(&wusbhc->mutex); + pr_err("%s: error usb dev gone\n", __func__); goto error_dev_gone; } wusb_dev = __wusb_dev_get_by_usb_dev(wusbhc, urb->dev); if (wusb_dev == NULL) { mutex_unlock(&wusbhc->mutex); + pr_err("%s: error wusb dev gone\n", __func__); goto error_dev_gone; } mutex_unlock(&wusbhc->mutex); @@ -1082,21 +1086,28 @@ static void wa_urb_enqueue_b(struct wa_xfer *xfer) spin_lock_irqsave(&xfer->lock, flags); xfer->wusb_dev = wusb_dev; result = urb->status; - if (urb->status != -EINPROGRESS) + if (urb->status != -EINPROGRESS) { + pr_err("%s: error_dequeued\n", __func__); goto error_dequeued; + } result = __wa_xfer_setup(xfer, urb); - if (result < 0) + if (result < 0) { + pr_err("%s: error_xfer_setup\n", __func__); goto error_xfer_setup; + } result = __wa_xfer_submit(xfer); - if (result < 0) + if (result < 0) { + pr_err("%s: error_xfer_submit\n", __func__); goto error_xfer_submit; + } spin_unlock_irqrestore(&xfer->lock, flags); - return; + return 0; - /* this is basically wa_xfer_completion() broken up wa_xfer_giveback() - * does a wa_xfer_put() that will call wa_xfer_destroy() and clean - * upundo setup(). + /* + * this is basically wa_xfer_completion() broken up wa_xfer_giveback() + * does a wa_xfer_put() that will call wa_xfer_destroy() and undo + * setup(). */ error_xfer_setup: error_dequeued: @@ -1108,8 +1119,7 @@ error_dev_gone: rpipe_put(xfer->ep->hcpriv); error_rpipe_get: xfer->result = result; - wa_xfer_giveback(xfer); - return; + return result; error_xfer_submit: done = __wa_xfer_is_done(xfer); @@ -1117,6 +1127,8 @@ error_xfer_submit: spin_unlock_irqrestore(&xfer->lock, flags); if (done) wa_xfer_completion(xfer); + /* return success since the completion routine will run. */ + return 0; } /* @@ -1150,7 +1162,8 @@ void wa_urb_enqueue_run(struct work_struct *ws) list_del_init(&xfer->list_node); urb = xfer->urb; - wa_urb_enqueue_b(xfer); + if (wa_urb_enqueue_b(xfer) < 0) + wa_xfer_giveback(xfer); usb_put_urb(urb); /* taken when queuing */ } } @@ -1256,7 +1269,19 @@ int wa_urb_enqueue(struct wahc *wa, struct usb_host_endpoint *ep, spin_unlock_irqrestore(&wa->xfer_list_lock, my_flags); queue_work(wusbd, &wa->xfer_enqueue_work); } else { - wa_urb_enqueue_b(xfer); + result = wa_urb_enqueue_b(xfer); + if (result < 0) { + /* + * URB submit/enqueue failed. Clean up, return an + * error and do not run the callback. This avoids + * an infinite submit/complete loop. + */ + dev_err(dev, "%s: URB enqueue failed: %d\n", + __func__, result); + wa_put(xfer->wa); + wa_xfer_put(xfer); + return result; + } } return 0; |