diff options
author | Ben Hutchings <ben@decadent.org.uk> | 2012-02-12 01:00:41 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2012-02-24 16:11:56 -0500 |
commit | 711c68b3c0f7a924ffbee4aa962d8f62b85188ff (patch) | |
tree | 364aa61ad7714ae92264ae06e304fc4fd8171dd0 /drivers/usb/class | |
parent | c192c8e71a2ded01170c1a992cd21aaedc822756 (diff) |
cdc-wdm: Fix more races on the read path
We must not allow the input buffer length to change while we're
shuffling the buffer contents. We also mustn't clear the WDM_READ
flag after more data might have arrived. Therefore move both of these
into the spinlocked region at the bottom of wdm_read().
When reading desc->length without holding the iuspin lock, use
ACCESS_ONCE() to ensure the compiler doesn't re-read it with
inconsistent results.
Signed-off-by: Ben Hutchings <ben@decadent.org.uk>
Tested-by: Bjørn Mork <bjorn@mork.no>
Cc: Oliver Neukum <oliver@neukum.org>
Cc: stable <stable@vger.kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/usb/class')
-rw-r--r-- | drivers/usb/class/cdc-wdm.c | 16 |
1 files changed, 11 insertions, 5 deletions
diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c index b27bbb957e16..7ca54d4dea92 100644 --- a/drivers/usb/class/cdc-wdm.c +++ b/drivers/usb/class/cdc-wdm.c | |||
@@ -391,7 +391,7 @@ outnl: | |||
391 | static ssize_t wdm_read | 391 | static ssize_t wdm_read |
392 | (struct file *file, char __user *buffer, size_t count, loff_t *ppos) | 392 | (struct file *file, char __user *buffer, size_t count, loff_t *ppos) |
393 | { | 393 | { |
394 | int rv, cntr = 0; | 394 | int rv, cntr; |
395 | int i = 0; | 395 | int i = 0; |
396 | struct wdm_device *desc = file->private_data; | 396 | struct wdm_device *desc = file->private_data; |
397 | 397 | ||
@@ -400,7 +400,8 @@ static ssize_t wdm_read | |||
400 | if (rv < 0) | 400 | if (rv < 0) |
401 | return -ERESTARTSYS; | 401 | return -ERESTARTSYS; |
402 | 402 | ||
403 | if (desc->length == 0) { | 403 | cntr = ACCESS_ONCE(desc->length); |
404 | if (cntr == 0) { | ||
404 | desc->read = 0; | 405 | desc->read = 0; |
405 | retry: | 406 | retry: |
406 | if (test_bit(WDM_DISCONNECTING, &desc->flags)) { | 407 | if (test_bit(WDM_DISCONNECTING, &desc->flags)) { |
@@ -455,25 +456,30 @@ retry: | |||
455 | goto retry; | 456 | goto retry; |
456 | } | 457 | } |
457 | clear_bit(WDM_READ, &desc->flags); | 458 | clear_bit(WDM_READ, &desc->flags); |
459 | cntr = desc->length; | ||
458 | spin_unlock_irq(&desc->iuspin); | 460 | spin_unlock_irq(&desc->iuspin); |
459 | } | 461 | } |
460 | 462 | ||
461 | cntr = count > desc->length ? desc->length : count; | 463 | if (cntr > count) |
464 | cntr = count; | ||
462 | rv = copy_to_user(buffer, desc->ubuf, cntr); | 465 | rv = copy_to_user(buffer, desc->ubuf, cntr); |
463 | if (rv > 0) { | 466 | if (rv > 0) { |
464 | rv = -EFAULT; | 467 | rv = -EFAULT; |
465 | goto err; | 468 | goto err; |
466 | } | 469 | } |
467 | 470 | ||
471 | spin_lock_irq(&desc->iuspin); | ||
472 | |||
468 | for (i = 0; i < desc->length - cntr; i++) | 473 | for (i = 0; i < desc->length - cntr; i++) |
469 | desc->ubuf[i] = desc->ubuf[i + cntr]; | 474 | desc->ubuf[i] = desc->ubuf[i + cntr]; |
470 | 475 | ||
471 | spin_lock_irq(&desc->iuspin); | ||
472 | desc->length -= cntr; | 476 | desc->length -= cntr; |
473 | spin_unlock_irq(&desc->iuspin); | ||
474 | /* in case we had outstanding data */ | 477 | /* in case we had outstanding data */ |
475 | if (!desc->length) | 478 | if (!desc->length) |
476 | clear_bit(WDM_READ, &desc->flags); | 479 | clear_bit(WDM_READ, &desc->flags); |
480 | |||
481 | spin_unlock_irq(&desc->iuspin); | ||
482 | |||
477 | rv = cntr; | 483 | rv = cntr; |
478 | 484 | ||
479 | err: | 485 | err: |