diff options
author | Alan Stern <stern@rowland.harvard.edu> | 2014-01-07 10:43:02 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2014-01-07 12:30:48 -0500 |
commit | 543d7784b07ffd16cc82a9cb4e1e0323fd0040f1 (patch) | |
tree | b19c472dd418d227842104249b5fd26c4f2f1d7b /drivers/usb/core | |
parent | 1f53b485294509281cb918d6372ab24e82a1959e (diff) |
USB: fix race between hub_disconnect and recursively_mark_NOTATTACHED
There is a race in the hub driver between hub_disconnect() and
recursively_mark_NOTATTACHED(). This race can be triggered if the
driver is unbound from a device at the same time as the bus's root hub
is removed. When the race occurs, it can cause an oops:
BUG: unable to handle kernel NULL pointer dereference at 0000015c
IP: [<c16d5fb0>] recursively_mark_NOTATTACHED+0x20/0x60
Call Trace:
[<c16d5fc4>] recursively_mark_NOTATTACHED+0x34/0x60
[<c16d5fc4>] recursively_mark_NOTATTACHED+0x34/0x60
[<c16d5fc4>] recursively_mark_NOTATTACHED+0x34/0x60
[<c16d5fc4>] recursively_mark_NOTATTACHED+0x34/0x60
[<c16d6082>] usb_set_device_state+0x92/0x120
[<c16d862b>] usb_disconnect+0x2b/0x1a0
[<c16dd4c0>] usb_remove_hcd+0xb0/0x160
[<c19ca846>] ? _raw_spin_unlock_irqrestore+0x26/0x50
[<c1704efc>] ehci_mid_remove+0x1c/0x30
[<c1704f26>] ehci_mid_stop_host+0x16/0x30
[<c16f7698>] penwell_otg_work+0xd28/0x3520
[<c19c945b>] ? __schedule+0x39b/0x7f0
[<c19cdb9d>] ? sub_preempt_count+0x3d/0x50
[<c125e97d>] process_one_work+0x11d/0x3d0
[<c19c7f4d>] ? mutex_unlock+0xd/0x10
[<c125e0e5>] ? manage_workers.isra.24+0x1b5/0x270
[<c125f009>] worker_thread+0xf9/0x320
[<c19ca846>] ? _raw_spin_unlock_irqrestore+0x26/0x50
[<c125ef10>] ? rescuer_thread+0x2b0/0x2b0
[<c1264ac4>] kthread+0x94/0xa0
[<c19d0f77>] ret_from_kernel_thread+0x1b/0x28
[<c1264a30>] ? kthread_create_on_node+0xc0/0xc0
One problem is that recursively_mark_NOTATTACHED() uses the intfdata
value and hub->hdev->maxchild while hub_disconnect() is clearing them.
Another problem is that it uses hub->ports[i] while the port device is
being released.
To fix this race, we need to hold the device_state_lock while
hub_disconnect() changes the values. (Note that usb_disconnect()
and hub_port_connect_change() already acquire this lock at similar
critical times during a USB device's life cycle.) We also need to
remove the port devices after maxchild has been set to 0, instead of
before.
Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Reported-by: "Du, Changbin" <changbinx.du@intel.com>
Tested-by: "Du, Changbin" <changbinx.du@intel.com>
CC: <stable@vger.kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/usb/core')
-rw-r--r-- | drivers/usb/core/hub.c | 14 |
1 files changed, 9 insertions, 5 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 92e052db27ac..9e49c6d6e4a2 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c | |||
@@ -1600,7 +1600,7 @@ static void hub_disconnect(struct usb_interface *intf) | |||
1600 | { | 1600 | { |
1601 | struct usb_hub *hub = usb_get_intfdata(intf); | 1601 | struct usb_hub *hub = usb_get_intfdata(intf); |
1602 | struct usb_device *hdev = interface_to_usbdev(intf); | 1602 | struct usb_device *hdev = interface_to_usbdev(intf); |
1603 | int i; | 1603 | int port1; |
1604 | 1604 | ||
1605 | /* Take the hub off the event list and don't let it be added again */ | 1605 | /* Take the hub off the event list and don't let it be added again */ |
1606 | spin_lock_irq(&hub_event_lock); | 1606 | spin_lock_irq(&hub_event_lock); |
@@ -1615,11 +1615,15 @@ static void hub_disconnect(struct usb_interface *intf) | |||
1615 | hub->error = 0; | 1615 | hub->error = 0; |
1616 | hub_quiesce(hub, HUB_DISCONNECT); | 1616 | hub_quiesce(hub, HUB_DISCONNECT); |
1617 | 1617 | ||
1618 | usb_set_intfdata (intf, NULL); | 1618 | /* Avoid races with recursively_mark_NOTATTACHED() */ |
1619 | spin_lock_irq(&device_state_lock); | ||
1620 | port1 = hdev->maxchild; | ||
1621 | hdev->maxchild = 0; | ||
1622 | usb_set_intfdata(intf, NULL); | ||
1623 | spin_unlock_irq(&device_state_lock); | ||
1619 | 1624 | ||
1620 | for (i = 0; i < hdev->maxchild; i++) | 1625 | for (; port1 > 0; --port1) |
1621 | usb_hub_remove_port_device(hub, i + 1); | 1626 | usb_hub_remove_port_device(hub, port1); |
1622 | hub->hdev->maxchild = 0; | ||
1623 | 1627 | ||
1624 | if (hub->hdev->speed == USB_SPEED_HIGH) | 1628 | if (hub->hdev->speed == USB_SPEED_HIGH) |
1625 | highspeed_hubs--; | 1629 | highspeed_hubs--; |