diff mbox series

[v2] usbnet: fix kernel crash after disconnect

Message ID 20190430141440.9469-1-Jan.Kloetzke@preh.de (mailing list archive)
State Superseded
Headers show
Series [v2] usbnet: fix kernel crash after disconnect | expand

Commit Message

Kloetzke Jan April 30, 2019, 2:15 p.m. UTC
When disconnecting cdc_ncm the kernel sporadically crashes shortly
after the disconnect:

  [   57.868812] Unable to handle kernel NULL pointer dereference at virtual address 00000000
  ...
  [   58.006653] PC is at 0x0
  [   58.009202] LR is at call_timer_fn+0xec/0x1b4
  [   58.013567] pc : [<0000000000000000>] lr : [<ffffff80080f5130>] pstate: 00000145
  [   58.020976] sp : ffffff8008003da0
  [   58.024295] x29: ffffff8008003da0 x28: 0000000000000001
  [   58.029618] x27: 000000000000000a x26: 0000000000000100
  [   58.034941] x25: 0000000000000000 x24: ffffff8008003e68
  [   58.040263] x23: 0000000000000000 x22: 0000000000000000
  [   58.045587] x21: 0000000000000000 x20: ffffffc68fac1808
  [   58.050910] x19: 0000000000000100 x18: 0000000000000000
  [   58.056232] x17: 0000007f885aff8c x16: 0000007f883a9f10
  [   58.061556] x15: 0000000000000001 x14: 000000000000006e
  [   58.066878] x13: 0000000000000000 x12: 00000000000000ba
  [   58.072201] x11: ffffffc69ff1db30 x10: 0000000000000020
  [   58.077524] x9 : 8000100008001000 x8 : 0000000000000001
  [   58.082847] x7 : 0000000000000800 x6 : ffffff8008003e70
  [   58.088169] x5 : ffffffc69ff17a28 x4 : 00000000ffff138b
  [   58.093492] x3 : 0000000000000000 x2 : 0000000000000000
  [   58.098814] x1 : 0000000000000000 x0 : 0000000000000000
  ...
  [   58.205800] [<          (null)>]           (null)
  [   58.210521] [<ffffff80080f5298>] expire_timers+0xa0/0x14c
  [   58.215937] [<ffffff80080f542c>] run_timer_softirq+0xe8/0x128
  [   58.221702] [<ffffff8008081120>] __do_softirq+0x298/0x348
  [   58.227118] [<ffffff80080a6304>] irq_exit+0x74/0xbc
  [   58.232009] [<ffffff80080e17dc>] __handle_domain_irq+0x78/0xac
  [   58.237857] [<ffffff8008080cf4>] gic_handle_irq+0x80/0xac
  ...

The crash happens roughly 125..130ms after the disconnect. This
correlates with the 'delay' timer that is started on certain USB tx/rx
errors in the URB completion handler.

The suspected problem is a race of usbnet_stop() with
usbnet_start_xmit(). In usbnet_stop() we call usbnet_terminate_urbs()
to cancel all URBs in flight. This only makes sense if no new URBs are
submitted concurrently, though. But the usbnet_start_xmit() can run at
the same time on another CPU which almost unconditionally submits an
URB. The error callback of the new URB will then schedule the timer
after it was already stopped.

The fix adds a check if the tx queue is stopped after the tx list lock
has been taken. This should reliably prevent the submission of new URBs
while usbnet_terminate_urbs() does its job. The same thing is done on
the rx side even though it might be safe due to other flags that are
checked there.

Signed-off-by: Jan Klötzke <Jan.Kloetzke@preh.de>
---
 drivers/net/usb/usbnet.c | 6 ++++++
 1 file changed, 6 insertions(+)

Comments

David Miller May 5, 2019, 7:45 a.m. UTC | #1
From: Kloetzke Jan <Jan.Kloetzke@preh.de>
Date: Tue, 30 Apr 2019 14:15:07 +0000

> @@ -1431,6 +1432,11 @@ netdev_tx_t usbnet_start_xmit (struct sk_buff *skb,
>  		spin_unlock_irqrestore(&dev->txq.lock, flags);
>  		goto drop;
>  	}
> +	if (WARN_ON(netif_queue_stopped(net))) {
> +		usb_autopm_put_interface_async(dev->intf);
> +		spin_unlock_irqrestore(&dev->txq.lock, flags);
> +		goto drop;
> +	}

If this is known to happen and is expected, then we should not warn.
Oliver Neukum May 6, 2019, 8:17 a.m. UTC | #2
On So, 2019-05-05 at 00:45 -0700, David Miller wrote:
> From: Kloetzke Jan <Jan.Kloetzke@preh.de>
> Date: Tue, 30 Apr 2019 14:15:07 +0000
> 
> > @@ -1431,6 +1432,11 @@ netdev_tx_t usbnet_start_xmit (struct sk_buff *skb,
> >               spin_unlock_irqrestore(&dev->txq.lock, flags);
> >               goto drop;
> >       }
> > +     if (WARN_ON(netif_queue_stopped(net))) {
> > +             usb_autopm_put_interface_async(dev->intf);
> > +             spin_unlock_irqrestore(&dev->txq.lock, flags);
> > +             goto drop;
> > +     }
> 
> If this is known to happen and is expected, then we should not warn.
> 

Hi,

yes this is the point. Can ndo_start_xmit() and ndo_stop() race?
If not, why does the patch fix the observed issue and what
prevents the race? Something is not clear here.

	Regards
		Oliver
Kloetzke Jan May 16, 2019, 7:10 a.m. UTC | #3
Am Montag, den 06.05.2019, 10:17 +0200 schrieb Oliver Neukum:
> On So, 2019-05-05 at 00:45 -0700, David Miller wrote:
> > From: Kloetzke Jan <Jan.Kloetzke@preh.de>
> > Date: Tue, 30 Apr 2019 14:15:07 +0000
> > 
> > > @@ -1431,6 +1432,11 @@ netdev_tx_t usbnet_start_xmit (struct sk_buff *skb,
> > >               spin_unlock_irqrestore(&dev->txq.lock, flags);
> > >               goto drop;
> > >       }
> > > +     if (WARN_ON(netif_queue_stopped(net))) {
> > > +             usb_autopm_put_interface_async(dev->intf);
> > > +             spin_unlock_irqrestore(&dev->txq.lock, flags);
> > > +             goto drop;
> > > +     }
> > 
> > If this is known to happen and is expected, then we should not warn.
> > 
> 
> Hi,
> 
> yes this is the point. Can ndo_start_xmit() and ndo_stop() race?
> If not, why does the patch fix the observed issue and what
> prevents the race? Something is not clear here.

Dave, could you shed some light on Olivers question? If the race can
happen then we can stick to v1 because the WARN_ON is indeed pointless.
Otherwise it's not clear why it made the problem go away for us and v2
may be the better option...

Regards,
Jan
David Miller May 21, 2019, 12:09 a.m. UTC | #4
From: Kloetzke Jan <Jan.Kloetzke@preh.de>
Date: Thu, 16 May 2019 07:10:30 +0000

> Am Montag, den 06.05.2019, 10:17 +0200 schrieb Oliver Neukum:
>> On So, 2019-05-05 at 00:45 -0700, David Miller wrote:
>> > From: Kloetzke Jan <Jan.Kloetzke@preh.de>
>> > Date: Tue, 30 Apr 2019 14:15:07 +0000
>> > 
>> > > @@ -1431,6 +1432,11 @@ netdev_tx_t usbnet_start_xmit (struct sk_buff *skb,
>> > >               spin_unlock_irqrestore(&dev->txq.lock, flags);
>> > >               goto drop;
>> > >       }
>> > > +     if (WARN_ON(netif_queue_stopped(net))) {
>> > > +             usb_autopm_put_interface_async(dev->intf);
>> > > +             spin_unlock_irqrestore(&dev->txq.lock, flags);
>> > > +             goto drop;
>> > > +     }
>> > 
>> > If this is known to happen and is expected, then we should not warn.
>> > 
>> 
>> yes this is the point. Can ndo_start_xmit() and ndo_stop() race?
>> If not, why does the patch fix the observed issue and what
>> prevents the race? Something is not clear here.
> 
> Dave, could you shed some light on Olivers question? If the race can
> happen then we can stick to v1 because the WARN_ON is indeed pointless.
> Otherwise it's not clear why it made the problem go away for us and v2
> may be the better option...

Yes I think they can race.   ->ndo_stop() executes and stops the queue,
then we get an RCU grace period so that all parallel executions of
->ndo_start_xmit() complete.

But I wonder, this can probably cause problems because some drivers have
"stop queue and re-check" logic, f.e. in drivers/net/tg3.c we have:

	if (unlikely(tg3_tx_avail(tnapi) <= (MAX_SKB_FRAGS + 1))) {
		netif_tx_stop_queue(txq);

		/* netif_tx_stop_queue() must be done before checking
		 * checking tx index in tg3_tx_avail() below, because in
		 * tg3_tx(), we update tx index before checking for
		 * netif_tx_queue_stopped().
		 */
		smp_mb();
		if (tg3_tx_avail(tnapi) > TG3_TX_WAKEUP_THRESH(tnapi))
			netif_tx_wake_queue(txq);
	}

which in the racey scenerio would undo ->ndo_stop()'s work which is
completely unexpected.

Hmmm...
Oliver Neukum May 21, 2019, 9:48 a.m. UTC | #5
On Do, 2019-05-16 at 07:10 +0000, Kloetzke Jan wrote:
> Am Montag, den 06.05.2019, 10:17 +0200 schrieb Oliver Neukum:
> > On So, 2019-05-05 at 00:45 -0700, David Miller wrote:
> > > From: Kloetzke Jan <Jan.Kloetzke@preh.de>
> > > Date: Tue, 30 Apr 2019 14:15:07 +0000
> > > 
> > > > @@ -1431,6 +1432,11 @@ netdev_tx_t usbnet_start_xmit (struct sk_buff *skb,
> > > >               spin_unlock_irqrestore(&dev->txq.lock, flags);
> > > >               goto drop;
> > > >       }
> > > > +     if (WARN_ON(netif_queue_stopped(net))) {
> > > > +             usb_autopm_put_interface_async(dev->intf);
> > > > +             spin_unlock_irqrestore(&dev->txq.lock, flags);
> > > > +             goto drop;
> > > > +     }
> > > 
> > > If this is known to happen and is expected, then we should not warn.
> > > 
> > 
> > Hi,
> > 
> > yes this is the point. Can ndo_start_xmit() and ndo_stop() race?
> > If not, why does the patch fix the observed issue and what
> > prevents the race? Something is not clear here.
> 
> Dave, could you shed some light on Olivers question? If the race can
> happen then we can stick to v1 because the WARN_ON is indeed pointless.
> Otherwise it's not clear why it made the problem go away for us and v2
> may be the better option...

Hi,

as Dave confirmed that the race exists, could you resubmit without
the WARN ?

	Regards
		Oliver
Kloetzke Jan May 21, 2019, 10:12 a.m. UTC | #6
Hi,

Am Dienstag, den 21.05.2019, 11:48 +0200 schrieb Oliver Neukum:
> On Do, 2019-05-16 at 07:10 +0000, Kloetzke Jan wrote:
> > Am Montag, den 06.05.2019, 10:17 +0200 schrieb Oliver Neukum:
> > > On So, 2019-05-05 at 00:45 -0700, David Miller wrote:
> > > > From: Kloetzke Jan <Jan.Kloetzke@preh.de>
> > > > Date: Tue, 30 Apr 2019 14:15:07 +0000
> > > > 
> > > > > @@ -1431,6 +1432,11 @@ netdev_tx_t usbnet_start_xmit (struct sk_buff *skb,
> > > > >               spin_unlock_irqrestore(&dev->txq.lock, flags);
> > > > >               goto drop;
> > > > >       }
> > > > > +     if (WARN_ON(netif_queue_stopped(net))) {
> > > > > +             usb_autopm_put_interface_async(dev->intf);
> > > > > +             spin_unlock_irqrestore(&dev->txq.lock, flags);
> > > > > +             goto drop;
> > > > > +     }
> > > > 
> > > > If this is known to happen and is expected, then we should not warn.
> > > > 
> > > 
> > > Hi,
> > > 
> > > yes this is the point. Can ndo_start_xmit() and ndo_stop() race?
> > > If not, why does the patch fix the observed issue and what
> > > prevents the race? Something is not clear here.
> > 
> > Dave, could you shed some light on Olivers question? If the race can
> > happen then we can stick to v1 because the WARN_ON is indeed pointless.
> > Otherwise it's not clear why it made the problem go away for us and v2
> > may be the better option...
> 
> Hi,
> 
> as Dave confirmed that the race exists, could you resubmit without
> the WARN ?

Why not just take v1 of the patch?

  https://lore.kernel.org/netdev/20190417091849.7475-1-Jan.Kloetzke@preh.de/

The original version was exactly the same, just without the WARN_ON().
Or is it required to send a v3 in this case?

Regards,
Jan
Oliver Neukum May 21, 2019, 11:42 a.m. UTC | #7
On Di, 2019-05-21 at 10:12 +0000, Kloetzke Jan wrote:
> 
> Why not just take v1 of the patch?
> 
>   https://lore.kernel.org/netdev/20190417091849.7475-1-Jan.Kloetzke@preh.de/
> 
> The original version was exactly the same, just without the WARN_ON().
> Or is it required to send a v3 in this case?

It will make things easier for Dave to pick up.

	Regards
		Oliver
diff mbox series

Patch

diff --git a/drivers/net/usb/usbnet.c b/drivers/net/usb/usbnet.c
index 504282af27e5..36bb0a4fc320 100644
--- a/drivers/net/usb/usbnet.c
+++ b/drivers/net/usb/usbnet.c
@@ -506,6 +506,7 @@  static int rx_submit (struct usbnet *dev, struct urb *urb, gfp_t flags)
 
 	if (netif_running (dev->net) &&
 	    netif_device_present (dev->net) &&
+	    test_bit(EVENT_DEV_OPEN, &dev->flags) &&
 	    !test_bit (EVENT_RX_HALT, &dev->flags) &&
 	    !test_bit (EVENT_DEV_ASLEEP, &dev->flags)) {
 		switch (retval = usb_submit_urb (urb, GFP_ATOMIC)) {
@@ -1431,6 +1432,11 @@  netdev_tx_t usbnet_start_xmit (struct sk_buff *skb,
 		spin_unlock_irqrestore(&dev->txq.lock, flags);
 		goto drop;
 	}
+	if (WARN_ON(netif_queue_stopped(net))) {
+		usb_autopm_put_interface_async(dev->intf);
+		spin_unlock_irqrestore(&dev->txq.lock, flags);
+		goto drop;
+	}
 
 #ifdef CONFIG_PM
 	/* if this triggers the device is still a sleep */