From: Ben Hutchings Date: Sun, 12 Feb 2012 06:00:41 +0000 (+0000) Subject: cdc-wdm: Fix more races on the read path X-Git-Tag: v3.2.14~122 X-Git-Url: https://git.openpandora.org/cgi-bin/gitweb.cgi?p=pandora-kernel.git;a=commitdiff_plain;h=8d272f9af55e8900fc96886bd72b158482ed55d0 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 Tested-by: Bjørn Mork Cc: Oliver Neukum Signed-off-by: Greg Kroah-Hartman --- diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c index fd4aee188fe9..eb650b78ee4f 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: