This patch fixes a race condition in two utility routines related removal 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. Cc: Alan Stern <stern@xxxxxxxxxxxxxxxxxxx> Cc: Greg Kroah-Hartman <greg@xxxxxxxxx> Signed-off-by: Christian Lamparter <chunkeey@xxxxxxxxxxxxxx> --- v0 -> v1 - Alan Stern's comments. BTW, what about usb_{unlink,kill,poison)_anchored_urbs? At first glance, these functions seem to be immune to this bug (delisting is done elsewhere), or is there something I've missed? Regards, Chr --- diff --git a/drivers/usb/core/urb.c b/drivers/usb/core/urb.c index 7c05555..7b893de 100644 --- a/drivers/usb/core/urb.c +++ b/drivers/usb/core/urb.c @@ -137,6 +137,15 @@ void usb_anchor_urb(struct urb *urb, struct usb_anchor *anchor) } EXPORT_SYMBOL_GPL(usb_anchor_urb); +static void __usb_unanchor_urb(struct urb *urb, struct usb_anchor *anchor) +{ + urb->anchor = NULL; + list_del(&urb->anchor_list); + usb_put_urb(urb); + if (list_empty(&anchor->urb_list)) + wake_up(&anchor->wait); +} + /** * usb_unanchor_urb - unanchors an URB * @urb: pointer to the urb to anchor @@ -156,17 +165,9 @@ void usb_unanchor_urb(struct urb *urb) return; spin_lock_irqsave(&anchor->lock, flags); - if (unlikely(anchor != urb->anchor)) { - /* we've lost the race to another thread */ - spin_unlock_irqrestore(&anchor->lock, flags); - return; - } - urb->anchor = NULL; - list_del(&urb->anchor_list); + if (likely(anchor == urb->anchor)) + __usb_unanchor_urb(urb, anchor); spin_unlock_irqrestore(&anchor->lock, flags); - usb_put_urb(urb); - if (list_empty(&anchor->urb_list)) - wake_up(&anchor->wait); } EXPORT_SYMBOL_GPL(usb_unanchor_urb); @@ -799,12 +800,11 @@ struct urb *usb_get_from_anchor(struct usb_anchor *anchor) victim = list_entry(anchor->urb_list.next, struct urb, anchor_list); usb_get_urb(victim); - spin_unlock_irqrestore(&anchor->lock, flags); - usb_unanchor_urb(victim); + __usb_unanchor_urb(victim, anchor); } else { - spin_unlock_irqrestore(&anchor->lock, flags); victim = NULL; } + spin_unlock_irqrestore(&anchor->lock, flags); return victim; } @@ -826,12 +826,7 @@ void usb_scuttle_anchored_urbs(struct usb_anchor *anchor) while (!list_empty(&anchor->urb_list)) { victim = list_entry(anchor->urb_list.prev, struct urb, anchor_list); - usb_get_urb(victim); - spin_unlock_irqrestore(&anchor->lock, flags); - /* this may free the URB */ - usb_unanchor_urb(victim); - usb_put_urb(victim); - spin_lock_irqsave(&anchor->lock, flags); + __usb_unanchor_urb(victim, anchor); } spin_unlock_irqrestore(&anchor->lock, flags); } -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html