aboutsummaryrefslogtreecommitdiffstats
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-04-02 12:26:59 -0400
commit3b804c7c32ed23181025a5184cae7e2d5a237f48 (patch)
tree07c9a9c9f7c0ea734dae8f2b46bae924f69d9788
parenta4b9552b4fa34648a5017901ed8042214a9dfc03 (diff)
cdc-wdm: Fix more races on the read path
commit 711c68b3c0f7a924ffbee4aa962d8f62b85188ff upstream. 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> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-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 90581a85134..9ad39db76c2 100644
--- a/drivers/usb/class/cdc-wdm.c
+++ b/drivers/usb/class/cdc-wdm.c
@@ -397,7 +397,7 @@ outnl:
397static ssize_t wdm_read 397static ssize_t wdm_read
398(struct file *file, char __user *buffer, size_t count, loff_t *ppos) 398(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
399{ 399{
400 int rv, cntr = 0; 400 int rv, cntr;
401 int i = 0; 401 int i = 0;
402 struct wdm_device *desc = file->private_data; 402 struct wdm_device *desc = file->private_data;
403 403
@@ -406,7 +406,8 @@ static ssize_t wdm_read
406 if (rv < 0) 406 if (rv < 0)
407 return -ERESTARTSYS; 407 return -ERESTARTSYS;
408 408
409 if (desc->length == 0) { 409 cntr = ACCESS_ONCE(desc->length);
410 if (cntr == 0) {
410 desc->read = 0; 411 desc->read = 0;
411retry: 412retry:
412 if (test_bit(WDM_DISCONNECTING, &desc->flags)) { 413 if (test_bit(WDM_DISCONNECTING, &desc->flags)) {
@@ -457,25 +458,30 @@ retry:
457 goto retry; 458 goto retry;
458 } 459 }
459 clear_bit(WDM_READ, &desc->flags); 460 clear_bit(WDM_READ, &desc->flags);
461 cntr = desc->length;
460 spin_unlock_irq(&desc->iuspin); 462 spin_unlock_irq(&desc->iuspin);
461 } 463 }
462 464
463 cntr = count > desc->length ? desc->length : count; 465 if (cntr > count)
466 cntr = count;
464 rv = copy_to_user(buffer, desc->ubuf, cntr); 467 rv = copy_to_user(buffer, desc->ubuf, cntr);
465 if (rv > 0) { 468 if (rv > 0) {
466 rv = -EFAULT; 469 rv = -EFAULT;
467 goto err; 470 goto err;
468 } 471 }
469 472
473 spin_lock_irq(&desc->iuspin);
474
470 for (i = 0; i < desc->length - cntr; i++) 475 for (i = 0; i < desc->length - cntr; i++)
471 desc->ubuf[i] = desc->ubuf[i + cntr]; 476 desc->ubuf[i] = desc->ubuf[i + cntr];
472 477
473 spin_lock_irq(&desc->iuspin);
474 desc->length -= cntr; 478 desc->length -= cntr;
475 spin_unlock_irq(&desc->iuspin);
476 /* in case we had outstanding data */ 479 /* in case we had outstanding data */
477 if (!desc->length) 480 if (!desc->length)
478 clear_bit(WDM_READ, &desc->flags); 481 clear_bit(WDM_READ, &desc->flags);
482
483 spin_unlock_irq(&desc->iuspin);
484
479 rv = cntr; 485 rv = cntr;
480 486
481err: 487err: