Commit 33186c44 authored by Thomas Pugliese's avatar Thomas Pugliese Committed by Greg Kroah-Hartman

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: default avatarThomas Pugliese <thomas.pugliese@gmail.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 02c123ee
...@@ -1052,7 +1052,7 @@ static int __wa_xfer_submit(struct wa_xfer *xfer) ...@@ -1052,7 +1052,7 @@ static int __wa_xfer_submit(struct wa_xfer *xfer)
* result never kicks in, the xfer will timeout from the USB code and * result never kicks in, the xfer will timeout from the USB code and
* dequeue() will be called. * 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; int result;
unsigned long flags; unsigned long flags;
...@@ -1063,18 +1063,22 @@ static void wa_urb_enqueue_b(struct wa_xfer *xfer) ...@@ -1063,18 +1063,22 @@ static void wa_urb_enqueue_b(struct wa_xfer *xfer)
unsigned done; unsigned done;
result = rpipe_get_by_ep(wa, xfer->ep, urb, xfer->gfp); 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; goto error_rpipe_get;
}
result = -ENODEV; result = -ENODEV;
/* FIXME: segmentation broken -- kills DWA */ /* FIXME: segmentation broken -- kills DWA */
mutex_lock(&wusbhc->mutex); /* get a WUSB dev */ mutex_lock(&wusbhc->mutex); /* get a WUSB dev */
if (urb->dev == NULL) { if (urb->dev == NULL) {
mutex_unlock(&wusbhc->mutex); mutex_unlock(&wusbhc->mutex);
pr_err("%s: error usb dev gone\n", __func__);
goto error_dev_gone; goto error_dev_gone;
} }
wusb_dev = __wusb_dev_get_by_usb_dev(wusbhc, urb->dev); wusb_dev = __wusb_dev_get_by_usb_dev(wusbhc, urb->dev);
if (wusb_dev == NULL) { if (wusb_dev == NULL) {
mutex_unlock(&wusbhc->mutex); mutex_unlock(&wusbhc->mutex);
pr_err("%s: error wusb dev gone\n", __func__);
goto error_dev_gone; goto error_dev_gone;
} }
mutex_unlock(&wusbhc->mutex); mutex_unlock(&wusbhc->mutex);
...@@ -1082,21 +1086,28 @@ static void wa_urb_enqueue_b(struct wa_xfer *xfer) ...@@ -1082,21 +1086,28 @@ static void wa_urb_enqueue_b(struct wa_xfer *xfer)
spin_lock_irqsave(&xfer->lock, flags); spin_lock_irqsave(&xfer->lock, flags);
xfer->wusb_dev = wusb_dev; xfer->wusb_dev = wusb_dev;
result = urb->status; result = urb->status;
if (urb->status != -EINPROGRESS) if (urb->status != -EINPROGRESS) {
pr_err("%s: error_dequeued\n", __func__);
goto error_dequeued; goto error_dequeued;
}
result = __wa_xfer_setup(xfer, urb); result = __wa_xfer_setup(xfer, urb);
if (result < 0) if (result < 0) {
pr_err("%s: error_xfer_setup\n", __func__);
goto error_xfer_setup; goto error_xfer_setup;
}
result = __wa_xfer_submit(xfer); result = __wa_xfer_submit(xfer);
if (result < 0) if (result < 0) {
pr_err("%s: error_xfer_submit\n", __func__);
goto error_xfer_submit; goto error_xfer_submit;
}
spin_unlock_irqrestore(&xfer->lock, flags); 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 * this is basically wa_xfer_completion() broken up wa_xfer_giveback()
* upundo setup(). * does a wa_xfer_put() that will call wa_xfer_destroy() and undo
* setup().
*/ */
error_xfer_setup: error_xfer_setup:
error_dequeued: error_dequeued:
...@@ -1108,8 +1119,7 @@ static void wa_urb_enqueue_b(struct wa_xfer *xfer) ...@@ -1108,8 +1119,7 @@ static void wa_urb_enqueue_b(struct wa_xfer *xfer)
rpipe_put(xfer->ep->hcpriv); rpipe_put(xfer->ep->hcpriv);
error_rpipe_get: error_rpipe_get:
xfer->result = result; xfer->result = result;
wa_xfer_giveback(xfer); return result;
return;
error_xfer_submit: error_xfer_submit:
done = __wa_xfer_is_done(xfer); done = __wa_xfer_is_done(xfer);
...@@ -1117,6 +1127,8 @@ static void wa_urb_enqueue_b(struct wa_xfer *xfer) ...@@ -1117,6 +1127,8 @@ static void wa_urb_enqueue_b(struct wa_xfer *xfer)
spin_unlock_irqrestore(&xfer->lock, flags); spin_unlock_irqrestore(&xfer->lock, flags);
if (done) if (done)
wa_xfer_completion(xfer); 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) ...@@ -1150,7 +1162,8 @@ void wa_urb_enqueue_run(struct work_struct *ws)
list_del_init(&xfer->list_node); list_del_init(&xfer->list_node);
urb = xfer->urb; 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 */ usb_put_urb(urb); /* taken when queuing */
} }
} }
...@@ -1256,7 +1269,19 @@ int wa_urb_enqueue(struct wahc *wa, struct usb_host_endpoint *ep, ...@@ -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); spin_unlock_irqrestore(&wa->xfer_list_lock, my_flags);
queue_work(wusbd, &wa->xfer_enqueue_work); queue_work(wusbd, &wa->xfer_enqueue_work);
} else { } 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; return 0;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment