aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/class
diff options
context:
space:
mode:
authorBen Hutchings <ben@decadent.org.uk>2012-02-12 01:00:41 -0500
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2012-02-24 16:11:56 -0500
commit711c68b3c0f7a924ffbee4aa962d8f62b85188ff (patch)
tree364aa61ad7714ae92264ae06e304fc4fd8171dd0 /drivers/usb/class
parentc192c8e71a2ded01170c1a992cd21aaedc822756 (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.c16
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:
391static ssize_t wdm_read 391static 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;
405retry: 406retry:
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
479err: 485err: