usbnet: include wait queue head in device structure

This fixes a race which happens by freeing an object on the stack.
Quoting Julius:
> The issue is
> that it calls usbnet_terminate_urbs() before that, which temporarily
> installs a waitqueue in dev->wait in order to be able to wait on the
> tasklet to run and finish up some queues. The waiting itself looks
> okay, but the access to 'dev->wait' is totally unprotected and can
> race arbitrarily. I think in this case usbnet_bh() managed to succeed
> it's dev->wait check just before usbnet_terminate_urbs() sets it back
> to NULL. The latter then finishes and the waitqueue_t structure on its
> stack gets overwritten by other functions halfway through the
> wake_up() call in usbnet_bh().

The fix is to just not allocate the data structure on the stack.
As dev->wait is abused as a flag it also takes a runtime PM change
to fix this bug.

Signed-off-by: Oliver Neukum <oneukum@suse.de>
Reported-by: Grant Grundler <grundler@google.com>
Tested-by: Grant Grundler <grundler@google.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Oliver Neukum 2014-03-26 14:32:51 +01:00 committed by David S. Miller
parent 681daee244
commit 14a0d635d1
2 changed files with 20 additions and 15 deletions

View file

@ -752,14 +752,12 @@ EXPORT_SYMBOL_GPL(usbnet_unlink_rx_urbs);
// precondition: never called in_interrupt // precondition: never called in_interrupt
static void usbnet_terminate_urbs(struct usbnet *dev) static void usbnet_terminate_urbs(struct usbnet *dev)
{ {
DECLARE_WAIT_QUEUE_HEAD_ONSTACK(unlink_wakeup);
DECLARE_WAITQUEUE(wait, current); DECLARE_WAITQUEUE(wait, current);
int temp; int temp;
/* ensure there are no more active urbs */ /* ensure there are no more active urbs */
add_wait_queue(&unlink_wakeup, &wait); add_wait_queue(&dev->wait, &wait);
set_current_state(TASK_UNINTERRUPTIBLE); set_current_state(TASK_UNINTERRUPTIBLE);
dev->wait = &unlink_wakeup;
temp = unlink_urbs(dev, &dev->txq) + temp = unlink_urbs(dev, &dev->txq) +
unlink_urbs(dev, &dev->rxq); unlink_urbs(dev, &dev->rxq);
@ -773,15 +771,14 @@ static void usbnet_terminate_urbs(struct usbnet *dev)
"waited for %d urb completions\n", temp); "waited for %d urb completions\n", temp);
} }
set_current_state(TASK_RUNNING); set_current_state(TASK_RUNNING);
dev->wait = NULL; remove_wait_queue(&dev->wait, &wait);
remove_wait_queue(&unlink_wakeup, &wait);
} }
int usbnet_stop (struct net_device *net) int usbnet_stop (struct net_device *net)
{ {
struct usbnet *dev = netdev_priv(net); struct usbnet *dev = netdev_priv(net);
struct driver_info *info = dev->driver_info; struct driver_info *info = dev->driver_info;
int retval; int retval, pm;
clear_bit(EVENT_DEV_OPEN, &dev->flags); clear_bit(EVENT_DEV_OPEN, &dev->flags);
netif_stop_queue (net); netif_stop_queue (net);
@ -791,6 +788,8 @@ int usbnet_stop (struct net_device *net)
net->stats.rx_packets, net->stats.tx_packets, net->stats.rx_packets, net->stats.tx_packets,
net->stats.rx_errors, net->stats.tx_errors); net->stats.rx_errors, net->stats.tx_errors);
/* to not race resume */
pm = usb_autopm_get_interface(dev->intf);
/* allow minidriver to stop correctly (wireless devices to turn off /* allow minidriver to stop correctly (wireless devices to turn off
* radio etc) */ * radio etc) */
if (info->stop) { if (info->stop) {
@ -817,6 +816,9 @@ int usbnet_stop (struct net_device *net)
dev->flags = 0; dev->flags = 0;
del_timer_sync (&dev->delay); del_timer_sync (&dev->delay);
tasklet_kill (&dev->bh); tasklet_kill (&dev->bh);
if (!pm)
usb_autopm_put_interface(dev->intf);
if (info->manage_power && if (info->manage_power &&
!test_and_clear_bit(EVENT_NO_RUNTIME_PM, &dev->flags)) !test_and_clear_bit(EVENT_NO_RUNTIME_PM, &dev->flags))
info->manage_power(dev, 0); info->manage_power(dev, 0);
@ -1437,11 +1439,12 @@ static void usbnet_bh (unsigned long param)
/* restart RX again after disabling due to high error rate */ /* restart RX again after disabling due to high error rate */
clear_bit(EVENT_RX_KILL, &dev->flags); clear_bit(EVENT_RX_KILL, &dev->flags);
// waiting for all pending urbs to complete? /* waiting for all pending urbs to complete?
if (dev->wait) { * only then can we forgo submitting anew
if ((dev->txq.qlen + dev->rxq.qlen + dev->done.qlen) == 0) { */
wake_up (dev->wait); if (waitqueue_active(&dev->wait)) {
} if (dev->txq.qlen + dev->rxq.qlen + dev->done.qlen == 0)
wake_up_all(&dev->wait);
// or are we maybe short a few urbs? // or are we maybe short a few urbs?
} else if (netif_running (dev->net) && } else if (netif_running (dev->net) &&
@ -1580,6 +1583,7 @@ usbnet_probe (struct usb_interface *udev, const struct usb_device_id *prod)
dev->driver_name = name; dev->driver_name = name;
dev->msg_enable = netif_msg_init (msg_level, NETIF_MSG_DRV dev->msg_enable = netif_msg_init (msg_level, NETIF_MSG_DRV
| NETIF_MSG_PROBE | NETIF_MSG_LINK); | NETIF_MSG_PROBE | NETIF_MSG_LINK);
init_waitqueue_head(&dev->wait);
skb_queue_head_init (&dev->rxq); skb_queue_head_init (&dev->rxq);
skb_queue_head_init (&dev->txq); skb_queue_head_init (&dev->txq);
skb_queue_head_init (&dev->done); skb_queue_head_init (&dev->done);
@ -1791,9 +1795,10 @@ int usbnet_resume (struct usb_interface *intf)
spin_unlock_irq(&dev->txq.lock); spin_unlock_irq(&dev->txq.lock);
if (test_bit(EVENT_DEV_OPEN, &dev->flags)) { if (test_bit(EVENT_DEV_OPEN, &dev->flags)) {
/* handle remote wakeup ASAP */ /* handle remote wakeup ASAP
if (!dev->wait && * we cannot race against stop
netif_device_present(dev->net) && */
if (netif_device_present(dev->net) &&
!timer_pending(&dev->delay) && !timer_pending(&dev->delay) &&
!test_bit(EVENT_RX_HALT, &dev->flags)) !test_bit(EVENT_RX_HALT, &dev->flags))
rx_alloc_submit(dev, GFP_NOIO); rx_alloc_submit(dev, GFP_NOIO);

View file

@ -30,7 +30,7 @@ struct usbnet {
struct driver_info *driver_info; struct driver_info *driver_info;
const char *driver_name; const char *driver_name;
void *driver_priv; void *driver_priv;
wait_queue_head_t *wait; wait_queue_head_t wait;
struct mutex phy_mutex; struct mutex phy_mutex;
unsigned char suspend_count; unsigned char suspend_count;
unsigned char pkt_cnt, pkt_err; unsigned char pkt_cnt, pkt_err;