diff options
author | Thomas Pugliese <thomas.pugliese@gmail.com> | 2014-03-04 12:24:55 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2014-03-07 15:39:39 -0500 |
commit | 2a6da97ff530650d26570a6a1ec0ac1deac927bd (patch) | |
tree | 64222edab88045ceda239c12a719fbcafd4889db /drivers/usb/wusbcore | |
parent | 3c1b2c3ecd3122fe6dded7b012f74021144d95b2 (diff) |
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/usb/wusbcore')
-rw-r--r-- | drivers/usb/wusbcore/wa-xfer.c | 8 |
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) | |||
1974 | goto out_unlock; /* setup(), enqueue_b() completes */ | 1974 | goto out_unlock; /* setup(), enqueue_b() completes */ |
1975 | /* Ok, the xfer is in flight already, it's been setup and submitted.*/ | 1975 | /* Ok, the xfer is in flight already, it's been setup and submitted.*/ |
1976 | xfer_abort_pending = __wa_xfer_abort(xfer) >= 0; | 1976 | xfer_abort_pending = __wa_xfer_abort(xfer) >= 0; |
1977 | /* | ||
1978 | * grab the rpipe->seg_lock here to prevent racing with | ||
1979 | * __wa_xfer_delayed_run. | ||
1980 | */ | ||
1981 | spin_lock(&rpipe->seg_lock); | ||
1977 | for (cnt = 0; cnt < xfer->segs; cnt++) { | 1982 | for (cnt = 0; cnt < xfer->segs; cnt++) { |
1978 | seg = xfer->seg[cnt]; | 1983 | seg = xfer->seg[cnt]; |
1979 | pr_debug("%s: xfer id 0x%08X#%d status = %d\n", | 1984 | 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) | |||
1994 | */ | 1999 | */ |
1995 | seg->status = WA_SEG_ABORTED; | 2000 | seg->status = WA_SEG_ABORTED; |
1996 | seg->result = -ENOENT; | 2001 | seg->result = -ENOENT; |
1997 | spin_lock_irqsave(&rpipe->seg_lock, flags2); | ||
1998 | list_del(&seg->list_node); | 2002 | list_del(&seg->list_node); |
1999 | xfer->segs_done++; | 2003 | xfer->segs_done++; |
2000 | spin_unlock_irqrestore(&rpipe->seg_lock, flags2); | ||
2001 | break; | 2004 | break; |
2002 | case WA_SEG_DONE: | 2005 | case WA_SEG_DONE: |
2003 | case WA_SEG_ERROR: | 2006 | case WA_SEG_ERROR: |
@@ -2026,6 +2029,7 @@ int wa_urb_dequeue(struct wahc *wa, struct urb *urb, int status) | |||
2026 | break; | 2029 | break; |
2027 | } | 2030 | } |
2028 | } | 2031 | } |
2032 | spin_unlock(&rpipe->seg_lock); | ||
2029 | xfer->result = urb->status; /* -ENOENT or -ECONNRESET */ | 2033 | xfer->result = urb->status; /* -ENOENT or -ECONNRESET */ |
2030 | done = __wa_xfer_is_done(xfer); | 2034 | done = __wa_xfer_is_done(xfer); |
2031 | spin_unlock_irqrestore(&xfer->lock, flags); | 2035 | spin_unlock_irqrestore(&xfer->lock, flags); |