aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/class
diff options
context:
space:
mode:
authorBen Hutchings <ben@decadent.org.uk>2012-02-12 06:00:41 +0000
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2012-04-02 09:26:59 -0700
commit3b804c7c32ed23181025a5184cae7e2d5a237f48 (patch)
tree07c9a9c9f7c0ea734dae8f2b46bae924f69d9788 /drivers/usb/class
parenta4b9552b4fa34648a5017901ed8042214a9dfc03 (diff)
downloadkernel_samsung_smdk4412-3b804c7c32ed23181025a5184cae7e2d5a237f48.zip
kernel_samsung_smdk4412-3b804c7c32ed23181025a5184cae7e2d5a237f48.tar.gz
kernel_samsung_smdk4412-3b804c7c32ed23181025a5184cae7e2d5a237f48.tar.bz2
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>
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 90581a8..9ad39db 100644
--- a/drivers/usb/class/cdc-wdm.c
+++ b/drivers/usb/class/cdc-wdm.c
@@ -397,7 +397,7 @@ outnl:
static ssize_t wdm_read
(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
{
- int rv, cntr = 0;
+ int rv, cntr;
int i = 0;
struct wdm_device *desc = file->private_data;
@@ -406,7 +406,8 @@ static ssize_t wdm_read
if (rv < 0)
return -ERESTARTSYS;
- if (desc->length == 0) {
+ cntr = ACCESS_ONCE(desc->length);
+ if (cntr == 0) {
desc->read = 0;
retry:
if (test_bit(WDM_DISCONNECTING, &desc->flags)) {
@@ -457,25 +458,30 @@ retry:
goto retry;
}
clear_bit(WDM_READ, &desc->flags);
+ cntr = desc->length;
spin_unlock_irq(&desc->iuspin);
}
- cntr = count > desc->length ? desc->length : count;
+ if (cntr > count)
+ cntr = count;
rv = copy_to_user(buffer, desc->ubuf, cntr);
if (rv > 0) {
rv = -EFAULT;
goto err;
}
+ spin_lock_irq(&desc->iuspin);
+
for (i = 0; i < desc->length - cntr; i++)
desc->ubuf[i] = desc->ubuf[i + cntr];
- spin_lock_irq(&desc->iuspin);
desc->length -= cntr;
- spin_unlock_irq(&desc->iuspin);
/* in case we had outstanding data */
if (!desc->length)
clear_bit(WDM_READ, &desc->flags);
+
+ spin_unlock_irq(&desc->iuspin);
+
rv = cntr;
err: