| From e50293ef9775c5f1cf3fcc093037dd6a8c5684ea Mon Sep 17 00:00:00 2001 |
| From: Alan Stern <stern@rowland.harvard.edu> |
| Date: Wed, 16 Dec 2015 13:32:38 -0500 |
| Subject: USB: fix invalid memory access in hub_activate() |
| |
| From: Alan Stern <stern@rowland.harvard.edu> |
| |
| commit e50293ef9775c5f1cf3fcc093037dd6a8c5684ea upstream. |
| |
| Commit 8520f38099cc ("USB: change hub initialization sleeps to |
| delayed_work") changed the hub_activate() routine to make part of it |
| run in a workqueue. However, the commit failed to take a reference to |
| the usb_hub structure or to lock the hub interface while doing so. As |
| a result, if a hub is plugged in and quickly unplugged before the work |
| routine can run, the routine will try to access memory that has been |
| deallocated. Or, if the hub is unplugged while the routine is |
| running, the memory may be deallocated while it is in active use. |
| |
| This patch fixes the problem by taking a reference to the usb_hub at |
| the start of hub_activate() and releasing it at the end (when the work |
| is finished), and by locking the hub interface while the work routine |
| is running. It also adds a check at the start of the routine to see |
| if the hub has already been disconnected, in which nothing should be |
| done. |
| |
| Signed-off-by: Alan Stern <stern@rowland.harvard.edu> |
| Reported-by: Alexandru Cornea <alexandru.cornea@intel.com> |
| Tested-by: Alexandru Cornea <alexandru.cornea@intel.com> |
| Fixes: 8520f38099cc ("USB: change hub initialization sleeps to delayed_work") |
| CC: <stable@vger.kernel.org> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| drivers/usb/core/hub.c | 22 +++++++++++++++++++--- |
| 1 file changed, 19 insertions(+), 3 deletions(-) |
| |
| --- a/drivers/usb/core/hub.c |
| +++ b/drivers/usb/core/hub.c |
| @@ -1034,10 +1034,20 @@ static void hub_activate(struct usb_hub |
| unsigned delay; |
| |
| /* Continue a partial initialization */ |
| - if (type == HUB_INIT2) |
| - goto init2; |
| - if (type == HUB_INIT3) |
| + if (type == HUB_INIT2 || type == HUB_INIT3) { |
| + device_lock(hub->intfdev); |
| + |
| + /* Was the hub disconnected while we were waiting? */ |
| + if (hub->disconnected) { |
| + device_unlock(hub->intfdev); |
| + kref_put(&hub->kref, hub_release); |
| + return; |
| + } |
| + if (type == HUB_INIT2) |
| + goto init2; |
| goto init3; |
| + } |
| + kref_get(&hub->kref); |
| |
| /* The superspeed hub except for root hub has to use Hub Depth |
| * value as an offset into the route string to locate the bits |
| @@ -1235,6 +1245,7 @@ static void hub_activate(struct usb_hub |
| queue_delayed_work(system_power_efficient_wq, |
| &hub->init_work, |
| msecs_to_jiffies(delay)); |
| + device_unlock(hub->intfdev); |
| return; /* Continues at init3: below */ |
| } else { |
| msleep(delay); |
| @@ -1256,6 +1267,11 @@ static void hub_activate(struct usb_hub |
| /* Allow autosuspend if it was suppressed */ |
| if (type <= HUB_INIT3) |
| usb_autopm_put_interface_async(to_usb_interface(hub->intfdev)); |
| + |
| + if (type == HUB_INIT2 || type == HUB_INIT3) |
| + device_unlock(hub->intfdev); |
| + |
| + kref_put(&hub->kref, hub_release); |
| } |
| |
| /* Implement the continuations for the delays above */ |