diff options
author | Huajun Li <huajun.li.lee@gmail.com> | 2012-05-18 08:12:51 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2012-06-01 03:12:57 -0400 |
commit | 87d8d621cdd1c47d0dc19118081412d3f8178e72 (patch) | |
tree | bdd07621614e45a0b56927507e26af3619bba145 | |
parent | 2960d811d562540494c83b96e4ae4b6b11196016 (diff) |
USB: Remove races in devio.c
commit 4e09dcf20f7b5358615514c2ec8584b248ab8874 upstream.
There exist races in devio.c, below is one case,
and there are similar races in destroy_async()
and proc_unlinkurb(). Remove these races.
cancel_bulk_urbs() async_completed()
------------------- -----------------------
spin_unlock(&ps->lock);
list_move_tail(&as->asynclist,
&ps->async_completed);
wake_up(&ps->wait);
Lead to free_async() be triggered,
then urb and 'as' will be freed.
usb_unlink_urb(as->urb);
===> refer to the freed 'as'
Signed-off-by: Huajun Li <huajun.li.lee@gmail.com>
Cc: Alan Stern <stern@rowland.harvard.edu>
Cc: Oncaphillis <oncaphillis@snafu.de>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r-- | drivers/usb/core/devio.c | 33 |
1 files changed, 25 insertions, 8 deletions
diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index 0ca54e22d31..ca3c303eed8 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c | |||
@@ -292,17 +292,14 @@ static struct async *async_getcompleted(struct dev_state *ps) | |||
292 | static struct async *async_getpending(struct dev_state *ps, | 292 | static struct async *async_getpending(struct dev_state *ps, |
293 | void __user *userurb) | 293 | void __user *userurb) |
294 | { | 294 | { |
295 | unsigned long flags; | ||
296 | struct async *as; | 295 | struct async *as; |
297 | 296 | ||
298 | spin_lock_irqsave(&ps->lock, flags); | ||
299 | list_for_each_entry(as, &ps->async_pending, asynclist) | 297 | list_for_each_entry(as, &ps->async_pending, asynclist) |
300 | if (as->userurb == userurb) { | 298 | if (as->userurb == userurb) { |
301 | list_del_init(&as->asynclist); | 299 | list_del_init(&as->asynclist); |
302 | spin_unlock_irqrestore(&ps->lock, flags); | ||
303 | return as; | 300 | return as; |
304 | } | 301 | } |
305 | spin_unlock_irqrestore(&ps->lock, flags); | 302 | |
306 | return NULL; | 303 | return NULL; |
307 | } | 304 | } |
308 | 305 | ||
@@ -357,6 +354,7 @@ static void cancel_bulk_urbs(struct dev_state *ps, unsigned bulk_addr) | |||
357 | __releases(ps->lock) | 354 | __releases(ps->lock) |
358 | __acquires(ps->lock) | 355 | __acquires(ps->lock) |
359 | { | 356 | { |
357 | struct urb *urb; | ||
360 | struct async *as; | 358 | struct async *as; |
361 | 359 | ||
362 | /* Mark all the pending URBs that match bulk_addr, up to but not | 360 | /* Mark all the pending URBs that match bulk_addr, up to but not |
@@ -379,8 +377,11 @@ __acquires(ps->lock) | |||
379 | list_for_each_entry(as, &ps->async_pending, asynclist) { | 377 | list_for_each_entry(as, &ps->async_pending, asynclist) { |
380 | if (as->bulk_status == AS_UNLINK) { | 378 | if (as->bulk_status == AS_UNLINK) { |
381 | as->bulk_status = 0; /* Only once */ | 379 | as->bulk_status = 0; /* Only once */ |
380 | urb = as->urb; | ||
381 | usb_get_urb(urb); | ||
382 | spin_unlock(&ps->lock); /* Allow completions */ | 382 | spin_unlock(&ps->lock); /* Allow completions */ |
383 | usb_unlink_urb(as->urb); | 383 | usb_unlink_urb(urb); |
384 | usb_put_urb(urb); | ||
384 | spin_lock(&ps->lock); | 385 | spin_lock(&ps->lock); |
385 | goto rescan; | 386 | goto rescan; |
386 | } | 387 | } |
@@ -433,6 +434,7 @@ static void async_completed(struct urb *urb) | |||
433 | 434 | ||
434 | static void destroy_async(struct dev_state *ps, struct list_head *list) | 435 | static void destroy_async(struct dev_state *ps, struct list_head *list) |
435 | { | 436 | { |
437 | struct urb *urb; | ||
436 | struct async *as; | 438 | struct async *as; |
437 | unsigned long flags; | 439 | unsigned long flags; |
438 | 440 | ||
@@ -440,10 +442,13 @@ static void destroy_async(struct dev_state *ps, struct list_head *list) | |||
440 | while (!list_empty(list)) { | 442 | while (!list_empty(list)) { |
441 | as = list_entry(list->next, struct async, asynclist); | 443 | as = list_entry(list->next, struct async, asynclist); |
442 | list_del_init(&as->asynclist); | 444 | list_del_init(&as->asynclist); |
445 | urb = as->urb; | ||
446 | usb_get_urb(urb); | ||
443 | 447 | ||
444 | /* drop the spinlock so the completion handler can run */ | 448 | /* drop the spinlock so the completion handler can run */ |
445 | spin_unlock_irqrestore(&ps->lock, flags); | 449 | spin_unlock_irqrestore(&ps->lock, flags); |
446 | usb_kill_urb(as->urb); | 450 | usb_kill_urb(urb); |
451 | usb_put_urb(urb); | ||
447 | spin_lock_irqsave(&ps->lock, flags); | 452 | spin_lock_irqsave(&ps->lock, flags); |
448 | } | 453 | } |
449 | spin_unlock_irqrestore(&ps->lock, flags); | 454 | spin_unlock_irqrestore(&ps->lock, flags); |
@@ -1352,12 +1357,24 @@ static int proc_submiturb(struct dev_state *ps, void __user *arg) | |||
1352 | 1357 | ||
1353 | static int proc_unlinkurb(struct dev_state *ps, void __user *arg) | 1358 | static int proc_unlinkurb(struct dev_state *ps, void __user *arg) |
1354 | { | 1359 | { |
1360 | struct urb *urb; | ||
1355 | struct async *as; | 1361 | struct async *as; |
1362 | unsigned long flags; | ||
1356 | 1363 | ||
1364 | spin_lock_irqsave(&ps->lock, flags); | ||
1357 | as = async_getpending(ps, arg); | 1365 | as = async_getpending(ps, arg); |
1358 | if (!as) | 1366 | if (!as) { |
1367 | spin_unlock_irqrestore(&ps->lock, flags); | ||
1359 | return -EINVAL; | 1368 | return -EINVAL; |
1360 | usb_kill_urb(as->urb); | 1369 | } |
1370 | |||
1371 | urb = as->urb; | ||
1372 | usb_get_urb(urb); | ||
1373 | spin_unlock_irqrestore(&ps->lock, flags); | ||
1374 | |||
1375 | usb_kill_urb(urb); | ||
1376 | usb_put_urb(urb); | ||
1377 | |||
1361 | return 0; | 1378 | return 0; |
1362 | } | 1379 | } |
1363 | 1380 | ||