diff mbox

usbnet: Fix a race between usbnet_stop() and the BH

Message ID 1441116333-5377-1-git-send-email-eugene.shatokhin@rosalab.ru
State Accepted, archived
Delegated to: David Miller
Headers show

Commit Message

Eugene Shatokhin Sept. 1, 2015, 2:05 p.m. UTC
The race may happen when a device (e.g. YOTA 4G LTE Modem) is
unplugged while the system is downloading a large file from the Net.

Hardware breakpoints and Kprobes with delays were used to confirm that
the race does actually happen.

The race is on skb_queue ('next' pointer) between usbnet_stop()
and rx_complete(), which, in turn, calls usbnet_bh().

Here is a part of the call stack with the code where the changes to the
queue happen. The line numbers are for the kernel 4.1.0:

*0 __skb_unlink (skbuff.h:1517)
    prev->next = next;
*1 defer_bh (usbnet.c:430)
    spin_lock_irqsave(&list->lock, flags);
    old_state = entry->state;
    entry->state = state;
    __skb_unlink(skb, list);
    spin_unlock(&list->lock);
    spin_lock(&dev->done.lock);
    __skb_queue_tail(&dev->done, skb);
    if (dev->done.qlen == 1)
        tasklet_schedule(&dev->bh);
    spin_unlock_irqrestore(&dev->done.lock, flags);
*2 rx_complete (usbnet.c:640)
    state = defer_bh(dev, skb, &dev->rxq, state);

At the same time, the following code repeatedly checks if the queue is
empty and reads these values concurrently with the above changes:

*0  usbnet_terminate_urbs (usbnet.c:765)
    /* maybe wait for deletions to finish. */
    while (!skb_queue_empty(&dev->rxq)
        && !skb_queue_empty(&dev->txq)
        && !skb_queue_empty(&dev->done)) {
            schedule_timeout(msecs_to_jiffies(UNLINK_TIMEOUT_MS));
            set_current_state(TASK_UNINTERRUPTIBLE);
            netif_dbg(dev, ifdown, dev->net,
                  "waited for %d urb completions\n", temp);
    }
*1  usbnet_stop (usbnet.c:806)
    if (!(info->flags & FLAG_AVOID_UNLINK_URBS))
        usbnet_terminate_urbs(dev);

As a result, it is possible, for example, that the skb is removed from
dev->rxq by __skb_unlink() before the check
"!skb_queue_empty(&dev->rxq)" in usbnet_terminate_urbs() is made. It is
also possible in this case that the skb is added to dev->done queue
after "!skb_queue_empty(&dev->done)" is checked. So
usbnet_terminate_urbs() may stop waiting and return while dev->done
queue still has an item.

Locking in defer_bh() and usbnet_terminate_urbs() was revisited to avoid
this race.

Signed-off-by: Eugene Shatokhin <eugene.shatokhin@rosalab.ru>
---
 drivers/net/usb/usbnet.c | 39 ++++++++++++++++++++++++++++-----------
 1 file changed, 28 insertions(+), 11 deletions(-)

Comments

Eugene Shatokhin Sept. 8, 2015, 7:24 a.m. UTC | #1
01.09.2015 17:05, Eugene Shatokhin пишет:
> The race may happen when a device (e.g. YOTA 4G LTE Modem) is
> unplugged while the system is downloading a large file from the Net.
>
> Hardware breakpoints and Kprobes with delays were used to confirm that
> the race does actually happen.
>
> The race is on skb_queue ('next' pointer) between usbnet_stop()
> and rx_complete(), which, in turn, calls usbnet_bh().
>
> Here is a part of the call stack with the code where the changes to the
> queue happen. The line numbers are for the kernel 4.1.0:
>
> *0 __skb_unlink (skbuff.h:1517)
>      prev->next = next;
> *1 defer_bh (usbnet.c:430)
>      spin_lock_irqsave(&list->lock, flags);
>      old_state = entry->state;
>      entry->state = state;
>      __skb_unlink(skb, list);
>      spin_unlock(&list->lock);
>      spin_lock(&dev->done.lock);
>      __skb_queue_tail(&dev->done, skb);
>      if (dev->done.qlen == 1)
>          tasklet_schedule(&dev->bh);
>      spin_unlock_irqrestore(&dev->done.lock, flags);
> *2 rx_complete (usbnet.c:640)
>      state = defer_bh(dev, skb, &dev->rxq, state);
>
> At the same time, the following code repeatedly checks if the queue is
> empty and reads these values concurrently with the above changes:
>
> *0  usbnet_terminate_urbs (usbnet.c:765)
>      /* maybe wait for deletions to finish. */
>      while (!skb_queue_empty(&dev->rxq)
>          && !skb_queue_empty(&dev->txq)
>          && !skb_queue_empty(&dev->done)) {
>              schedule_timeout(msecs_to_jiffies(UNLINK_TIMEOUT_MS));
>              set_current_state(TASK_UNINTERRUPTIBLE);
>              netif_dbg(dev, ifdown, dev->net,
>                    "waited for %d urb completions\n", temp);
>      }
> *1  usbnet_stop (usbnet.c:806)
>      if (!(info->flags & FLAG_AVOID_UNLINK_URBS))
>          usbnet_terminate_urbs(dev);
>
> As a result, it is possible, for example, that the skb is removed from
> dev->rxq by __skb_unlink() before the check
> "!skb_queue_empty(&dev->rxq)" in usbnet_terminate_urbs() is made. It is
> also possible in this case that the skb is added to dev->done queue
> after "!skb_queue_empty(&dev->done)" is checked. So
> usbnet_terminate_urbs() may stop waiting and return while dev->done
> queue still has an item.
>
> Locking in defer_bh() and usbnet_terminate_urbs() was revisited to avoid
> this race.
>
> Signed-off-by: Eugene Shatokhin <eugene.shatokhin@rosalab.ru>
> ---
>   drivers/net/usb/usbnet.c | 39 ++++++++++++++++++++++++++++-----------
>   1 file changed, 28 insertions(+), 11 deletions(-)
>
> diff --git a/drivers/net/usb/usbnet.c b/drivers/net/usb/usbnet.c
> index e049857..b4cf107 100644
> --- a/drivers/net/usb/usbnet.c
> +++ b/drivers/net/usb/usbnet.c
> @@ -428,12 +428,18 @@ static enum skb_state defer_bh(struct usbnet *dev, struct sk_buff *skb,
>   	old_state = entry->state;
>   	entry->state = state;
>   	__skb_unlink(skb, list);
> -	spin_unlock(&list->lock);
> -	spin_lock(&dev->done.lock);
> +
> +	/* defer_bh() is never called with list == &dev->done.
> +	 * spin_lock_nested() tells lockdep that it is OK to take
> +	 * dev->done.lock here with list->lock held.
> +	 */
> +	spin_lock_nested(&dev->done.lock, SINGLE_DEPTH_NESTING);
> +
>   	__skb_queue_tail(&dev->done, skb);
>   	if (dev->done.qlen == 1)
>   		tasklet_schedule(&dev->bh);
> -	spin_unlock_irqrestore(&dev->done.lock, flags);
> +	spin_unlock(&dev->done.lock);
> +	spin_unlock_irqrestore(&list->lock, flags);
>   	return old_state;
>   }
>
> @@ -749,6 +755,20 @@ EXPORT_SYMBOL_GPL(usbnet_unlink_rx_urbs);
>
>   /*-------------------------------------------------------------------------*/
>
> +static void wait_skb_queue_empty(struct sk_buff_head *q)
> +{
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&q->lock, flags);
> +	while (!skb_queue_empty(q)) {
> +		spin_unlock_irqrestore(&q->lock, flags);
> +		schedule_timeout(msecs_to_jiffies(UNLINK_TIMEOUT_MS));
> +		set_current_state(TASK_UNINTERRUPTIBLE);
> +		spin_lock_irqsave(&q->lock, flags);
> +	}
> +	spin_unlock_irqrestore(&q->lock, flags);
> +}
> +
>   // precondition: never called in_interrupt
>   static void usbnet_terminate_urbs(struct usbnet *dev)
>   {
> @@ -762,14 +782,11 @@ static void usbnet_terminate_urbs(struct usbnet *dev)
>   		unlink_urbs(dev, &dev->rxq);
>
>   	/* maybe wait for deletions to finish. */
> -	while (!skb_queue_empty(&dev->rxq)
> -		&& !skb_queue_empty(&dev->txq)
> -		&& !skb_queue_empty(&dev->done)) {
> -			schedule_timeout(msecs_to_jiffies(UNLINK_TIMEOUT_MS));
> -			set_current_state(TASK_UNINTERRUPTIBLE);
> -			netif_dbg(dev, ifdown, dev->net,
> -				  "waited for %d urb completions\n", temp);
> -	}
> +	wait_skb_queue_empty(&dev->rxq);
> +	wait_skb_queue_empty(&dev->txq);
> +	wait_skb_queue_empty(&dev->done);
> +	netif_dbg(dev, ifdown, dev->net,
> +		  "waited for %d urb completions\n", temp);
>   	set_current_state(TASK_RUNNING);
>   	remove_wait_queue(&dev->wait, &wait);
>   }
>

I resent the patch to make it separate. What is the status of this now?

Regards,
Eugene
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Bjørn Mork Sept. 8, 2015, 7:37 a.m. UTC | #2
Eugene Shatokhin <eugene.shatokhin@rosalab.ru> writes:

> I resent the patch to make it separate. What is the status of this now?

One of the many nice features of patchwork is that you don't need to ask
those questions :)

See http://patchwork.ozlabs.org/patch/512856/

I really don't think it's appropriate for me to ack this, but I can add
my

Reviewed-by: Bjørn Mork <bjorn@mork.no>

if that helps.


Bjørn
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Oliver Neukum Sept. 8, 2015, 7:48 a.m. UTC | #3
On Tue, 2015-09-08 at 09:37 +0200, Bjørn Mork wrote:
> Eugene Shatokhin <eugene.shatokhin@rosalab.ru> writes:
> 
> > I resent the patch to make it separate. What is the status of this now?
> 
> One of the many nice features of patchwork is that you don't need to ask
> those questions :)
> 
> See http://patchwork.ozlabs.org/patch/512856/
> 
> I really don't think it's appropriate for me to ack this, but I can add
> my

Well, in case this helps:

> Reviewed-by: Bjørn Mork <bjorn@mork.no>
Acked-by: Oliver Neukum <oneukum@suse.com>

	Regards
		Oliver



--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
David Miller Sept. 8, 2015, 8:18 p.m. UTC | #4
From: Eugene Shatokhin <eugene.shatokhin@rosalab.ru>
Date: Tue,  1 Sep 2015 17:05:33 +0300

> The race may happen when a device (e.g. YOTA 4G LTE Modem) is
> unplugged while the system is downloading a large file from the Net.
> 
> Hardware breakpoints and Kprobes with delays were used to confirm that
> the race does actually happen.
> 
> The race is on skb_queue ('next' pointer) between usbnet_stop()
> and rx_complete(), which, in turn, calls usbnet_bh().
> 
> Here is a part of the call stack with the code where the changes to the
> queue happen. The line numbers are for the kernel 4.1.0:
 ...
> As a result, it is possible, for example, that the skb is removed from
> dev->rxq by __skb_unlink() before the check
> "!skb_queue_empty(&dev->rxq)" in usbnet_terminate_urbs() is made. It is
> also possible in this case that the skb is added to dev->done queue
> after "!skb_queue_empty(&dev->done)" is checked. So
> usbnet_terminate_urbs() may stop waiting and return while dev->done
> queue still has an item.
> 
> Locking in defer_bh() and usbnet_terminate_urbs() was revisited to avoid
> this race.
> 
> Signed-off-by: Eugene Shatokhin <eugene.shatokhin@rosalab.ru>

Applied, thanks.
--
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/drivers/net/usb/usbnet.c b/drivers/net/usb/usbnet.c
index e049857..b4cf107 100644
--- a/drivers/net/usb/usbnet.c
+++ b/drivers/net/usb/usbnet.c
@@ -428,12 +428,18 @@  static enum skb_state defer_bh(struct usbnet *dev, struct sk_buff *skb,
 	old_state = entry->state;
 	entry->state = state;
 	__skb_unlink(skb, list);
-	spin_unlock(&list->lock);
-	spin_lock(&dev->done.lock);
+
+	/* defer_bh() is never called with list == &dev->done.
+	 * spin_lock_nested() tells lockdep that it is OK to take
+	 * dev->done.lock here with list->lock held.
+	 */
+	spin_lock_nested(&dev->done.lock, SINGLE_DEPTH_NESTING);
+
 	__skb_queue_tail(&dev->done, skb);
 	if (dev->done.qlen == 1)
 		tasklet_schedule(&dev->bh);
-	spin_unlock_irqrestore(&dev->done.lock, flags);
+	spin_unlock(&dev->done.lock);
+	spin_unlock_irqrestore(&list->lock, flags);
 	return old_state;
 }
 
@@ -749,6 +755,20 @@  EXPORT_SYMBOL_GPL(usbnet_unlink_rx_urbs);
 
 /*-------------------------------------------------------------------------*/
 
+static void wait_skb_queue_empty(struct sk_buff_head *q)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&q->lock, flags);
+	while (!skb_queue_empty(q)) {
+		spin_unlock_irqrestore(&q->lock, flags);
+		schedule_timeout(msecs_to_jiffies(UNLINK_TIMEOUT_MS));
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		spin_lock_irqsave(&q->lock, flags);
+	}
+	spin_unlock_irqrestore(&q->lock, flags);
+}
+
 // precondition: never called in_interrupt
 static void usbnet_terminate_urbs(struct usbnet *dev)
 {
@@ -762,14 +782,11 @@  static void usbnet_terminate_urbs(struct usbnet *dev)
 		unlink_urbs(dev, &dev->rxq);
 
 	/* maybe wait for deletions to finish. */
-	while (!skb_queue_empty(&dev->rxq)
-		&& !skb_queue_empty(&dev->txq)
-		&& !skb_queue_empty(&dev->done)) {
-			schedule_timeout(msecs_to_jiffies(UNLINK_TIMEOUT_MS));
-			set_current_state(TASK_UNINTERRUPTIBLE);
-			netif_dbg(dev, ifdown, dev->net,
-				  "waited for %d urb completions\n", temp);
-	}
+	wait_skb_queue_empty(&dev->rxq);
+	wait_skb_queue_empty(&dev->txq);
+	wait_skb_queue_empty(&dev->done);
+	netif_dbg(dev, ifdown, dev->net,
+		  "waited for %d urb completions\n", temp);
 	set_current_state(TASK_RUNNING);
 	remove_wait_queue(&dev->wait, &wait);
 }