[PATCH v2] input: xpad: Prevent corruption of urb request.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



The xpad_send_led_command() and xpad_play_effect() functions submit urb requests, but do not
wait for the previous urb request to complete before using the same resources to
submit a new request.  This can lead to unpredictable undesirable effects, including
memory corruption and dereferencing of NULL pointers(and needless to say, kernel
panics).

Fix the issue by introducing a busy flag set on submission of the urb request, and
cleared on urb request completion.  If this flag is set while in xpad_send_led_command()
or xpad_play_effect(), the led/rumble packet data is buffered, and will be sent from
the urb request completion routine when the current urb request finishes.


Patch tested with rumble against a Logitech F510 and an X-Box v1 pad.


Signed-off-by: Sarah Bessmer <aotos@xxxxxxxxxxx>
---
v2:
-fixed introduced race in xpad_irq_out()

--- linux-3.14.4/drivers/input/joystick/xpad.c.orig	2014-05-15 13:13:39.000000000 -0700
+++ linux-3.14.4/drivers/input/joystick/xpad.c	2014-05-16 02:00:56.000000000 -0700
@@ -78,6 +78,7 @@
 #include <linux/stat.h>
 #include <linux/module.h>
 #include <linux/usb/input.h>
+#include <linux/spinlock.h>
 
 #define DRIVER_AUTHOR "Marko Friedemann <mfr@xxxxxxxxxxxxxxx>"
 #define DRIVER_DESC "X-Box pad driver"
@@ -282,7 +283,15 @@ struct usb_xpad {
 	struct urb *irq_out;		/* urb for interrupt out report */
 	unsigned char *odata;		/* output data */
 	dma_addr_t odata_dma;
-	struct mutex odata_mutex;
+	int odata_busy;
+
+	spinlock_t pend_lock;
+
+	unsigned pend_rum;
+	unsigned char rum_data[XPAD_PKT_LEN];
+
+	unsigned pend_led;
+	unsigned char led_data[XPAD_PKT_LEN];
 #endif
 
 #if defined(CONFIG_JOYSTICK_XPAD_LEDS)
@@ -541,12 +550,37 @@ static void xpad_irq_out(struct urb *urb
 	struct usb_xpad *xpad = urb->context;
 	struct device *dev = &xpad->intf->dev;
 	int retval, status;
+	unsigned long flags;
 
 	status = urb->status;
 
 	switch (status) {
 	case 0:
 		/* success */
+		spin_lock_irqsave(&xpad->pend_lock, flags);
+		xpad->irq_out->transfer_buffer_length = 0;
+
+		if (xpad->pend_rum != 0) {
+			memcpy(xpad->odata, xpad->rum_data, xpad->pend_rum);
+			xpad->irq_out->transfer_buffer_length = xpad->pend_rum;
+			xpad->pend_rum = 0;
+		} else if (xpad->pend_led != 0) {
+			memcpy(xpad->odata, xpad->led_data, xpad->pend_led);
+			xpad->irq_out->transfer_buffer_length = xpad->pend_led;
+			xpad->pend_led = 0;
+		}
+
+		if (xpad->irq_out->transfer_buffer_length != 0) {
+			spin_unlock_irqrestore(&xpad->pend_lock, flags);
+			if (usb_submit_urb(xpad->irq_out, GFP_ATOMIC) != 0) {
+				spin_lock_irqsave(&xpad->pend_lock, flags);
+				xpad->odata_busy = 0;
+				spin_unlock_irqrestore(&xpad->pend_lock, flags);
+			}
+		} else {
+			xpad->odata_busy = 0;
+			spin_unlock_irqrestore(&xpad->pend_lock, flags);
+		}
 		return;
 
 	case -ECONNRESET:
@@ -555,19 +589,27 @@ static void xpad_irq_out(struct urb *urb
 		/* this urb is terminated, clean up */
 		dev_dbg(dev, "%s - urb shutting down with status: %d\n",
 			__func__, status);
+
+		spin_lock_irqsave(&xpad->pend_lock, flags);
+		xpad->odata_busy = 0;
+		spin_unlock_irqrestore(&xpad->pend_lock, flags);
 		return;
 
 	default:
 		dev_dbg(dev, "%s - nonzero urb status received: %d\n",
 			__func__, status);
-		goto exit;
-	}
 
-exit:
-	retval = usb_submit_urb(urb, GFP_ATOMIC);
-	if (retval)
-		dev_err(dev, "%s - usb_submit_urb failed with result %d\n",
-			__func__, retval);
+		retval = usb_submit_urb(urb, GFP_ATOMIC);
+		if (retval) {
+			dev_err(dev, "%s - usb_submit_urb failed with result %d\n",
+				__func__, retval);
+			spin_lock_irqsave(&xpad->pend_lock, flags);
+			xpad->odata_busy = 0;
+			spin_unlock_irqrestore(&xpad->pend_lock, flags);
+		}
+
+		return;
+	}
 }
 
 static int xpad_init_output(struct usb_interface *intf, struct usb_xpad *xpad)
@@ -585,7 +627,12 @@ static int xpad_init_output(struct usb_i
 		goto fail1;
 	}
 
-	mutex_init(&xpad->odata_mutex);
+	xpad->odata_busy = 0;
+
+	spin_lock_init(&xpad->pend_lock);
+
+	xpad->pend_rum = 0;
+	xpad->pend_led = 0;
 
 	xpad->irq_out = usb_alloc_urb(0, GFP_KERNEL);
 	if (!xpad->irq_out) {
@@ -628,61 +675,91 @@ static void xpad_stop_output(struct usb_
 #endif
 
 #ifdef CONFIG_JOYSTICK_XPAD_FF
+static int xpad_make_rum_data(struct usb_xpad *xpad, __u16 strong, __u16 weak)
+{
+	switch (xpad->xtype) {
+
+	case XTYPE_XBOX:
+		xpad->rum_data[0] = 0x00;
+		xpad->rum_data[1] = 0x06;
+		xpad->rum_data[2] = 0x00;
+		xpad->rum_data[3] = strong / 256;	/* left actuator */
+		xpad->rum_data[4] = 0x00;
+		xpad->rum_data[5] = weak / 256;	/* right actuator */
+		xpad->pend_rum = 6;
+		return 0;
+
+
+	case XTYPE_XBOX360:
+		xpad->rum_data[0] = 0x00;
+		xpad->rum_data[1] = 0x08;
+		xpad->rum_data[2] = 0x00;
+		xpad->rum_data[3] = strong / 256;  /* left actuator? */
+		xpad->rum_data[4] = weak / 256;	/* right actuator? */
+		xpad->rum_data[5] = 0x00;
+		xpad->rum_data[6] = 0x00;
+		xpad->rum_data[7] = 0x00;
+		xpad->pend_rum = 8;
+		return 0;
+
+	case XTYPE_XBOX360W:
+		xpad->rum_data[0] = 0x00;
+		xpad->rum_data[1] = 0x01;
+		xpad->rum_data[2] = 0x0F;
+		xpad->rum_data[3] = 0xC0;
+		xpad->rum_data[4] = 0x00;
+		xpad->rum_data[5] = strong / 256;
+		xpad->rum_data[6] = weak / 256;
+		xpad->rum_data[7] = 0x00;
+		xpad->rum_data[8] = 0x00;
+		xpad->rum_data[9] = 0x00;
+		xpad->rum_data[10] = 0x00;
+		xpad->rum_data[11] = 0x00;
+		xpad->pend_rum = 12;
+		return 0;
+
+	}
+	return -1;
+}
+
 static int xpad_play_effect(struct input_dev *dev, void *data, struct ff_effect *effect)
 {
 	struct usb_xpad *xpad = input_get_drvdata(dev);
 
 	if (effect->type == FF_RUMBLE) {
-		__u16 strong = effect->u.rumble.strong_magnitude;
-		__u16 weak = effect->u.rumble.weak_magnitude;
-
-		switch (xpad->xtype) {
+		unsigned long flags;
+		int mrdrv;
 
-		case XTYPE_XBOX:
-			xpad->odata[0] = 0x00;
-			xpad->odata[1] = 0x06;
-			xpad->odata[2] = 0x00;
-			xpad->odata[3] = strong / 256;	/* left actuator */
-			xpad->odata[4] = 0x00;
-			xpad->odata[5] = weak / 256;	/* right actuator */
-			xpad->irq_out->transfer_buffer_length = 6;
-
-			return usb_submit_urb(xpad->irq_out, GFP_ATOMIC);
-
-		case XTYPE_XBOX360:
-			xpad->odata[0] = 0x00;
-			xpad->odata[1] = 0x08;
-			xpad->odata[2] = 0x00;
-			xpad->odata[3] = strong / 256;  /* left actuator? */
-			xpad->odata[4] = weak / 256;	/* right actuator? */
-			xpad->odata[5] = 0x00;
-			xpad->odata[6] = 0x00;
-			xpad->odata[7] = 0x00;
-			xpad->irq_out->transfer_buffer_length = 8;
-
-			return usb_submit_urb(xpad->irq_out, GFP_ATOMIC);
-
-		case XTYPE_XBOX360W:
-			xpad->odata[0] = 0x00;
-			xpad->odata[1] = 0x01;
-			xpad->odata[2] = 0x0F;
-			xpad->odata[3] = 0xC0;
-			xpad->odata[4] = 0x00;
-			xpad->odata[5] = strong / 256;
-			xpad->odata[6] = weak / 256;
-			xpad->odata[7] = 0x00;
-			xpad->odata[8] = 0x00;
-			xpad->odata[9] = 0x00;
-			xpad->odata[10] = 0x00;
-			xpad->odata[11] = 0x00;
-			xpad->irq_out->transfer_buffer_length = 12;
-
-			return usb_submit_urb(xpad->irq_out, GFP_ATOMIC);
+		spin_lock_irqsave(&xpad->pend_lock, flags);
+		mrdrv = xpad_make_rum_data(xpad, effect->u.rumble.strong_magnitude,
+						effect->u.rumble.weak_magnitude);
+
+		if (mrdrv == 0 && !xpad->odata_busy) {
+			memcpy(xpad->odata, xpad->rum_data, xpad->pend_rum);
+			xpad->irq_out->transfer_buffer_length = xpad->pend_rum;
+			xpad->pend_rum = 0;
+			xpad->odata_busy = 1;
+			spin_unlock_irqrestore(&xpad->pend_lock, flags);
+
+			if (usb_submit_urb(xpad->irq_out, GFP_ATOMIC) != 0) {
+				spin_lock_irqsave(&xpad->pend_lock, flags);
+				xpad->odata_busy = 0;
+				spin_unlock_irqrestore(&xpad->pend_lock, flags);
+			}
+		} else {
+			spin_unlock_irqrestore(&xpad->pend_lock, flags);
+
+			if (mrdrv == 0) {
+				dev_dbg(&xpad->dev->dev,
+					"%s - rumble while urb busy\n", __func__);
+			}
+		}
 
-		default:
+		if (mrdrv != 0) {
 			dev_dbg(&xpad->dev->dev,
 				"%s - rumble command sent to unsupported xpad type: %d\n",
 				__func__, xpad->xtype);
+
 			return -1;
 		}
 	}
@@ -716,13 +793,30 @@ struct xpad_led {
 static void xpad_send_led_command(struct usb_xpad *xpad, int command)
 {
 	if (command >= 0 && command < 14) {
-		mutex_lock(&xpad->odata_mutex);
-		xpad->odata[0] = 0x01;
-		xpad->odata[1] = 0x03;
-		xpad->odata[2] = command;
-		xpad->irq_out->transfer_buffer_length = 3;
-		usb_submit_urb(xpad->irq_out, GFP_KERNEL);
-		mutex_unlock(&xpad->odata_mutex);
+		unsigned long flags;
+
+		spin_lock_irqsave(&xpad->pend_lock, flags);
+		xpad->led_data[0] = 0x01;
+		xpad->led_data[1] = 0x03;
+		xpad->led_data[2] = command;
+		xpad->pend_led = 3;
+
+		if (!xpad->odata_busy) {
+			memcpy(xpad->odata, xpad->led_data, xpad->pend_led);
+			xpad->irq_out->transfer_buffer_length = xpad->pend_led;
+			xpad->pend_led = 0;
+			xpad->odata_busy = 1;
+			spin_unlock_irqrestore(&xpad->pend_lock, flags);
+
+			if (usb_submit_urb(xpad->irq_out, GFP_KERNEL) != 0) {
+				spin_lock_irqsave(&xpad->pend_lock, flags);
+				xpad->odata_busy = 0;
+				spin_unlock_irqrestore(&xpad->pend_lock, flags);
+			}
+		} else {
+			spin_unlock_irqrestore(&xpad->pend_lock, flags);
+			dev_dbg(&xpad->dev->dev, "%s - led while urb busy\n", __func__);
+		}
 	}
 }
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux Media Devel]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Linux Wireless Networking]     [Linux Omap]

  Powered by Linux