aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Lamparter <chunkeey@googlemail.com>2010-08-02 20:32:28 -0400
committerGreg Kroah-Hartman <gregkh@suse.de>2010-08-10 17:35:45 -0400
commitb3e670443b7fb8a2d29831b62b44a039c283e351 (patch)
tree094f9d9449d63acb2eefed77794a27308614c8ed
parent951fd8ee6a9493f80bc5bccf2c8cfa1535c6ce15 (diff)
USB: fix thread-unsafe anchor utiliy routines
This patch fixes a race condition in two utility routines related to the removal/unlinking of urbs from an anchor. If two threads are concurrently accessing the same anchor, both could end up with the same urb - thinking they are the exclusive owner. Alan Stern pointed out a related issue in usb_unlink_anchored_urbs: "The URB isn't removed from the anchor until it completes (as a by-product of completion, in fact), which might not be for quite some time after the unlink call returns. In the meantime, the subroutine will keep trying to unlink it, over and over again." Cc: stable <stable@kernel.org> Cc: Oliver Neukum <oneukum@suse.de> Cc: Greg Kroah-Hartman <greg@kroah.com> Acked-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Christian Lamparter <chunkeey@googlemail.com> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
-rw-r--r--drivers/usb/core/urb.c50
1 files changed, 21 insertions, 29 deletions
diff --git a/drivers/usb/core/urb.c b/drivers/usb/core/urb.c
index 7c0555548ac8..419e6b34e2fe 100644
--- a/drivers/usb/core/urb.c
+++ b/drivers/usb/core/urb.c
@@ -137,6 +137,16 @@ void usb_anchor_urb(struct urb *urb, struct usb_anchor *anchor)
137} 137}
138EXPORT_SYMBOL_GPL(usb_anchor_urb); 138EXPORT_SYMBOL_GPL(usb_anchor_urb);
139 139
140/* Callers must hold anchor->lock */
141static void __usb_unanchor_urb(struct urb *urb, struct usb_anchor *anchor)
142{
143 urb->anchor = NULL;
144 list_del(&urb->anchor_list);
145 usb_put_urb(urb);
146 if (list_empty(&anchor->urb_list))
147 wake_up(&anchor->wait);
148}
149
140/** 150/**
141 * usb_unanchor_urb - unanchors an URB 151 * usb_unanchor_urb - unanchors an URB
142 * @urb: pointer to the urb to anchor 152 * @urb: pointer to the urb to anchor
@@ -156,17 +166,14 @@ void usb_unanchor_urb(struct urb *urb)
156 return; 166 return;
157 167
158 spin_lock_irqsave(&anchor->lock, flags); 168 spin_lock_irqsave(&anchor->lock, flags);
159 if (unlikely(anchor != urb->anchor)) { 169 /*
160 /* we've lost the race to another thread */ 170 * At this point, we could be competing with another thread which
161 spin_unlock_irqrestore(&anchor->lock, flags); 171 * has the same intention. To protect the urb from being unanchored
162 return; 172 * twice, only the winner of the race gets the job.
163 } 173 */
164 urb->anchor = NULL; 174 if (likely(anchor == urb->anchor))
165 list_del(&urb->anchor_list); 175 __usb_unanchor_urb(urb, anchor);
166 spin_unlock_irqrestore(&anchor->lock, flags); 176 spin_unlock_irqrestore(&anchor->lock, flags);
167 usb_put_urb(urb);
168 if (list_empty(&anchor->urb_list))
169 wake_up(&anchor->wait);
170} 177}
171EXPORT_SYMBOL_GPL(usb_unanchor_urb); 178EXPORT_SYMBOL_GPL(usb_unanchor_urb);
172 179
@@ -749,20 +756,11 @@ EXPORT_SYMBOL_GPL(usb_unpoison_anchored_urbs);
749void usb_unlink_anchored_urbs(struct usb_anchor *anchor) 756void usb_unlink_anchored_urbs(struct usb_anchor *anchor)
750{ 757{
751 struct urb *victim; 758 struct urb *victim;
752 unsigned long flags;
753 759
754 spin_lock_irqsave(&anchor->lock, flags); 760 while ((victim = usb_get_from_anchor(anchor)) != NULL) {
755 while (!list_empty(&anchor->urb_list)) {
756 victim = list_entry(anchor->urb_list.prev, struct urb,
757 anchor_list);
758 usb_get_urb(victim);
759 spin_unlock_irqrestore(&anchor->lock, flags);
760 /* this will unanchor the URB */
761 usb_unlink_urb(victim); 761 usb_unlink_urb(victim);
762 usb_put_urb(victim); 762 usb_put_urb(victim);
763 spin_lock_irqsave(&anchor->lock, flags);
764 } 763 }
765 spin_unlock_irqrestore(&anchor->lock, flags);
766} 764}
767EXPORT_SYMBOL_GPL(usb_unlink_anchored_urbs); 765EXPORT_SYMBOL_GPL(usb_unlink_anchored_urbs);
768 766
@@ -799,12 +797,11 @@ struct urb *usb_get_from_anchor(struct usb_anchor *anchor)
799 victim = list_entry(anchor->urb_list.next, struct urb, 797 victim = list_entry(anchor->urb_list.next, struct urb,
800 anchor_list); 798 anchor_list);
801 usb_get_urb(victim); 799 usb_get_urb(victim);
802 spin_unlock_irqrestore(&anchor->lock, flags); 800 __usb_unanchor_urb(victim, anchor);
803 usb_unanchor_urb(victim);
804 } else { 801 } else {
805 spin_unlock_irqrestore(&anchor->lock, flags);
806 victim = NULL; 802 victim = NULL;
807 } 803 }
804 spin_unlock_irqrestore(&anchor->lock, flags);
808 805
809 return victim; 806 return victim;
810} 807}
@@ -826,12 +823,7 @@ void usb_scuttle_anchored_urbs(struct usb_anchor *anchor)
826 while (!list_empty(&anchor->urb_list)) { 823 while (!list_empty(&anchor->urb_list)) {
827 victim = list_entry(anchor->urb_list.prev, struct urb, 824 victim = list_entry(anchor->urb_list.prev, struct urb,
828 anchor_list); 825 anchor_list);
829 usb_get_urb(victim); 826 __usb_unanchor_urb(victim, anchor);
830 spin_unlock_irqrestore(&anchor->lock, flags);
831 /* this may free the URB */
832 usb_unanchor_urb(victim);
833 usb_put_urb(victim);
834 spin_lock_irqsave(&anchor->lock, flags);
835 } 827 }
836 spin_unlock_irqrestore(&anchor->lock, flags); 828 spin_unlock_irqrestore(&anchor->lock, flags);
837} 829}