summary refs log tree commit diff
path: root/drivers
diff options
context:
space:
mode:
authorThomas Pugliese <thomas.pugliese@gmail.com>2014-03-04 11:24:55 -0600
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2014-03-07 12:39:39 -0800
commit2a6da97ff530650d26570a6a1ec0ac1deac927bd (patch)
tree64222edab88045ceda239c12a719fbcafd4889db /drivers
parent3c1b2c3ecd3122fe6dded7b012f74021144d95b2 (diff)
downloadlinux-2a6da97ff530650d26570a6a1ec0ac1deac927bd.tar.gz
usb: wusbcore: fix potential double list_del on urb dequeue
This patch locks rpipe->seg_lock around the entire transfer segment
cleanup loop in wa_urb_dequeue instead of just one case of the switch
statement.  This fixes a race between __wa_xfer_delayed_run and
wa_urb_dequeue where a transfer segment in the WA_SEG_DELAYED state
could be removed from the rpipe seg_list twice leading to memory
corruption.  It also switches the spin_lock call to use the non-irqsave
version since the xfer->lock is already held and irqs already disabled.

Signed-off-by: Thomas Pugliese <thomas.pugliese@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/usb/wusbcore/wa-xfer.c8
1 files changed, 6 insertions, 2 deletions
diff --git a/drivers/usb/wusbcore/wa-xfer.c b/drivers/usb/wusbcore/wa-xfer.c
index 404ce81b286c..6e0d377d437c 100644
--- a/drivers/usb/wusbcore/wa-xfer.c
+++ b/drivers/usb/wusbcore/wa-xfer.c
@@ -1974,6 +1974,11 @@ int wa_urb_dequeue(struct wahc *wa, struct urb *urb, int status)
 		goto out_unlock;	/* setup(), enqueue_b() completes */
 	/* Ok, the xfer is in flight already, it's been setup and submitted.*/
 	xfer_abort_pending = __wa_xfer_abort(xfer) >= 0;
+	/*
+	 * grab the rpipe->seg_lock here to prevent racing with
+	 * __wa_xfer_delayed_run.
+	 */
+	spin_lock(&rpipe->seg_lock);
 	for (cnt = 0; cnt < xfer->segs; cnt++) {
 		seg = xfer->seg[cnt];
 		pr_debug("%s: xfer id 0x%08X#%d status = %d\n",
@@ -1994,10 +1999,8 @@ int wa_urb_dequeue(struct wahc *wa, struct urb *urb, int status)
 			 */
 			seg->status = WA_SEG_ABORTED;
 			seg->result = -ENOENT;
-			spin_lock_irqsave(&rpipe->seg_lock, flags2);
 			list_del(&seg->list_node);
 			xfer->segs_done++;
-			spin_unlock_irqrestore(&rpipe->seg_lock, flags2);
 			break;
 		case WA_SEG_DONE:
 		case WA_SEG_ERROR:
@@ -2026,6 +2029,7 @@ int wa_urb_dequeue(struct wahc *wa, struct urb *urb, int status)
 			break;
 		}
 	}
+	spin_unlock(&rpipe->seg_lock);
 	xfer->result = urb->status;	/* -ENOENT or -ECONNRESET */
 	done = __wa_xfer_is_done(xfer);
 	spin_unlock_irqrestore(&xfer->lock, flags);