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:
parent
681daee244
commit
14a0d635d1
2 changed files with 20 additions and 15 deletions
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue