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: |
