Vulnerability: Heap-based buffer overflow in the wdm_in_callback function in drivers/usb/class/cdc-wdm.c in the Linux kernel before 3.8.4 allows physically proximate attackers to cause a denial of service (system crash) or possibly execute arbitrary code via a crafted cdc-wdm USB device.
Vulnerability ID: CVE-2013-1860
CWE: CWE-119
Severity score: 10
Project: https://github.com/torvalds/linux
Published: 2013-03-22
File: drivers/usb/class/cdc-wdm.c
Commit message: USB: cdc-wdm: fix buffer overflow

The buffer for responses must not overflow.
If this would happen, set a flag, drop the data and return
an error after user space has read all remaining data.

Signed-off-by: Oliver Neukum <oliver@neukum.org>
CC: stable@kernel.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Commit author: Oliver Neukum

Vulnerable version (before fix):
 MODULE_DEVICE_TABLE (usb, wdm_ids); #define WDM_RESPONDING		7 #define WDM_SUSPENDING		8 #define WDM_RESETTING		9  #define WDM_MAX			16  static void wdm_in_callback(struct urb *urb) { 	struct wdm_device *desc = urb->context; 	int status = urb->status;  	spin_lock(&desc->iuspin); 	clear_bit(WDM_RESPONDING, &desc->flags); static void wdm_in_callback(struct urb *urb) 	}  	desc->rerr = status; 	desc->reslength = urb->actual_length; 	memmove(desc->ubuf + desc->length, desc->inbuf, desc->reslength); 	desc->length += desc->reslength; skip_error: 	wake_up(&desc->wait);  static ssize_t wdm_read 			rv = -ENODEV; 			goto err; 		} 		i++; 		if (file->f_flags & O_NONBLOCK) { 			if (!test_bit(WDM_READ, &desc->flags)) { static ssize_t wdm_read 			spin_unlock_irq(&desc->iuspin); 			goto retry; 		} 		if (!desc->reslength) { /* zero length read */ 			dev_dbg(&desc->intf->dev, "%s: zero length - clearing WDM_READ\n", __func__); 			clear_bit(WDM_READ, &desc->flags); static int wdm_post_reset(struct usb_interface *intf) 	struct wdm_device *desc = wdm_find_device(intf); 	int rv;  	clear_bit(WDM_RESETTING, &desc->flags); 	rv = recover_from_urb_loss(desc); 	mutex_unlock(&desc->wlock);

Fixed version (after fix):
 MODULE_DEVICE_TABLE (usb, wdm_ids); #define WDM_RESPONDING		7 #define WDM_SUSPENDING		8 #define WDM_RESETTING		9 #define WDM_OVERFLOW		10  #define WDM_MAX			16  static void wdm_in_callback(struct urb *urb) { 	struct wdm_device *desc = urb->context; 	int status = urb->status; 	int length = urb->actual_length;  	spin_lock(&desc->iuspin); 	clear_bit(WDM_RESPONDING, &desc->flags); static void wdm_in_callback(struct urb *urb) 	}  	desc->rerr = status; 	if (length + desc->length > desc->wMaxCommand) { 		/* The buffer would overflow */ 		set_bit(WDM_OVERFLOW, &desc->flags); 	} else { 		/* we may already be in overflow */ 		if (!test_bit(WDM_OVERFLOW, &desc->flags)) { 			memmove(desc->ubuf + desc->length, desc->inbuf, length); 			desc->length += length; 			desc->reslength = length; 		} 	} skip_error: 	wake_up(&desc->wait);  static ssize_t wdm_read 			rv = -ENODEV; 			goto err; 		} 		if (test_bit(WDM_OVERFLOW, &desc->flags)) { 			clear_bit(WDM_OVERFLOW, &desc->flags); 			rv = -ENOBUFS; 			goto err; 		} 		i++; 		if (file->f_flags & O_NONBLOCK) { 			if (!test_bit(WDM_READ, &desc->flags)) { static ssize_t wdm_read 			spin_unlock_irq(&desc->iuspin); 			goto retry; 		}  		if (!desc->reslength) { /* zero length read */ 			dev_dbg(&desc->intf->dev, "%s: zero length - clearing WDM_READ\n", __func__); 			clear_bit(WDM_READ, &desc->flags); static int wdm_post_reset(struct usb_interface *intf) 	struct wdm_device *desc = wdm_find_device(intf); 	int rv;  	clear_bit(WDM_OVERFLOW, &desc->flags); 	clear_bit(WDM_RESETTING, &desc->flags); 	rv = recover_from_urb_loss(desc); 	mutex_unlock(&desc->wlock);

Patch:
patch: @@ -56,6 +56,7 @@ MODULE_DEVICE_TABLE (usb, wdm_ids);
 #define WDM_RESPONDING		7
 #define WDM_SUSPENDING		8
 #define WDM_RESETTING		9
+#define WDM_OVERFLOW		10
 
 #define WDM_MAX			16
 
@@ -155,6 +156,7 @@ static void wdm_in_callback(struct urb *urb)
 {
 	struct wdm_device *desc = urb->context;
 	int status = urb->status;
+	int length = urb->actual_length;
 
 	spin_lock(&desc->iuspin);
 	clear_bit(WDM_RESPONDING, &desc->flags);
@@ -185,9 +187,17 @@ static void wdm_in_callback(struct urb *urb)
 	}
 
 	desc->rerr = status;
-	desc->reslength = urb->actual_length;
-	memmove(desc->ubuf + desc->length, desc->inbuf, desc->reslength);
-	desc->length += desc->reslength;
+	if (length + desc->length > desc->wMaxCommand) {
+		/* The buffer would overflow */
+		set_bit(WDM_OVERFLOW, &desc->flags);
+	} else {
+		/* we may already be in overflow */
+		if (!test_bit(WDM_OVERFLOW, &desc->flags)) {
+			memmove(desc->ubuf + desc->length, desc->inbuf, length);
+			desc->length += length;
+			desc->reslength = length;
+		}
+	}
 skip_error:
 	wake_up(&desc->wait);
 
@@ -435,6 +445,11 @@ static ssize_t wdm_read
 			rv = -ENODEV;
 			goto err;
 		}
+		if (test_bit(WDM_OVERFLOW, &desc->flags)) {
+			clear_bit(WDM_OVERFLOW, &desc->flags);
+			rv = -ENOBUFS;
+			goto err;
+		}
 		i++;
 		if (file->f_flags & O_NONBLOCK) {
 			if (!test_bit(WDM_READ, &desc->flags)) {
@@ -478,6 +493,7 @@ static ssize_t wdm_read
 			spin_unlock_irq(&desc->iuspin);
 			goto retry;
 		}
+
 		if (!desc->reslength) { /* zero length read */
 			dev_dbg(&desc->intf->dev, "%s: zero length - clearing WDM_READ\n", __func__);
 			clear_bit(WDM_READ, &desc->flags);
@@ -1004,6 +1020,7 @@ static int wdm_post_reset(struct usb_interface *intf)
 	struct wdm_device *desc = wdm_find_device(intf);
 	int rv;
 
+	clear_bit(WDM_OVERFLOW, &desc->flags);
 	clear_bit(WDM_RESETTING, &desc->flags);
 	rv = recover_from_urb_loss(desc);
 	mutex_unlock(&desc->wlock);
path: drivers/usb/class/cdc-wdm.c