Commit eb7f5a49 authored by Johan Hovold's avatar Johan Hovold Committed by Greg Kroah-Hartman
Browse files

USB: usblcd: fix I/O after disconnect



Make sure to stop all I/O on disconnect by adding a disconnected flag
which is used to prevent new I/O from being started and by stopping all
ongoing I/O before returning.

This also fixes a potential use-after-free on driver unbind in case the
driver data is freed before the completion handler has run.

Fixes: 1da177e4 ("Linux-2.6.12-rc2")
Cc: stable <stable@vger.kernel.org>	# 7bbe990c
Signed-off-by: default avatarJohan Hovold <johan@kernel.org>
Link: https://lore.kernel.org/r/20190926091228.24634-7-johan@kernel.org


Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 97639265
Loading
Loading
Loading
Loading
+31 −2
Original line number Original line Diff line number Diff line
@@ -18,6 +18,7 @@
#include <linux/slab.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/mutex.h>
#include <linux/rwsem.h>
#include <linux/uaccess.h>
#include <linux/uaccess.h>
#include <linux/usb.h>
#include <linux/usb.h>


@@ -57,6 +58,8 @@ struct usb_lcd {
							   using up all RAM */
							   using up all RAM */
	struct usb_anchor	submitted;		/* URBs to wait for
	struct usb_anchor	submitted;		/* URBs to wait for
							   before suspend */
							   before suspend */
	struct rw_semaphore	io_rwsem;
	unsigned long		disconnected:1;
};
};
#define to_lcd_dev(d) container_of(d, struct usb_lcd, kref)
#define to_lcd_dev(d) container_of(d, struct usb_lcd, kref)


@@ -142,6 +145,13 @@ static ssize_t lcd_read(struct file *file, char __user * buffer,


	dev = file->private_data;
	dev = file->private_data;


	down_read(&dev->io_rwsem);

	if (dev->disconnected) {
		retval = -ENODEV;
		goto out_up_io;
	}

	/* do a blocking bulk read to get data from the device */
	/* do a blocking bulk read to get data from the device */
	retval = usb_bulk_msg(dev->udev,
	retval = usb_bulk_msg(dev->udev,
			      usb_rcvbulkpipe(dev->udev,
			      usb_rcvbulkpipe(dev->udev,
@@ -158,6 +168,9 @@ static ssize_t lcd_read(struct file *file, char __user * buffer,
			retval = bytes_read;
			retval = bytes_read;
	}
	}


out_up_io:
	up_read(&dev->io_rwsem);

	return retval;
	return retval;
}
}


@@ -237,11 +250,18 @@ static ssize_t lcd_write(struct file *file, const char __user * user_buffer,
	if (r < 0)
	if (r < 0)
		return -EINTR;
		return -EINTR;


	down_read(&dev->io_rwsem);

	if (dev->disconnected) {
		retval = -ENODEV;
		goto err_up_io;
	}

	/* create a urb, and a buffer for it, and copy the data to the urb */
	/* create a urb, and a buffer for it, and copy the data to the urb */
	urb = usb_alloc_urb(0, GFP_KERNEL);
	urb = usb_alloc_urb(0, GFP_KERNEL);
	if (!urb) {
	if (!urb) {
		retval = -ENOMEM;
		retval = -ENOMEM;
		goto err_no_buf;
		goto err_up_io;
	}
	}


	buf = usb_alloc_coherent(dev->udev, count, GFP_KERNEL,
	buf = usb_alloc_coherent(dev->udev, count, GFP_KERNEL,
@@ -278,6 +298,7 @@ static ssize_t lcd_write(struct file *file, const char __user * user_buffer,
	   the USB core will eventually free it entirely */
	   the USB core will eventually free it entirely */
	usb_free_urb(urb);
	usb_free_urb(urb);


	up_read(&dev->io_rwsem);
exit:
exit:
	return count;
	return count;
error_unanchor:
error_unanchor:
@@ -285,7 +306,8 @@ error_unanchor:
error:
error:
	usb_free_coherent(dev->udev, count, buf, urb->transfer_dma);
	usb_free_coherent(dev->udev, count, buf, urb->transfer_dma);
	usb_free_urb(urb);
	usb_free_urb(urb);
err_no_buf:
err_up_io:
	up_read(&dev->io_rwsem);
	up(&dev->limit_sem);
	up(&dev->limit_sem);
	return retval;
	return retval;
}
}
@@ -325,6 +347,7 @@ static int lcd_probe(struct usb_interface *interface,


	kref_init(&dev->kref);
	kref_init(&dev->kref);
	sema_init(&dev->limit_sem, USB_LCD_CONCURRENT_WRITES);
	sema_init(&dev->limit_sem, USB_LCD_CONCURRENT_WRITES);
	init_rwsem(&dev->io_rwsem);
	init_usb_anchor(&dev->submitted);
	init_usb_anchor(&dev->submitted);


	dev->udev = usb_get_dev(interface_to_usbdev(interface));
	dev->udev = usb_get_dev(interface_to_usbdev(interface));
@@ -422,6 +445,12 @@ static void lcd_disconnect(struct usb_interface *interface)
	/* give back our minor */
	/* give back our minor */
	usb_deregister_dev(interface, &lcd_class);
	usb_deregister_dev(interface, &lcd_class);


	down_write(&dev->io_rwsem);
	dev->disconnected = 1;
	up_write(&dev->io_rwsem);

	usb_kill_anchored_urbs(&dev->submitted);

	/* decrement our usage count */
	/* decrement our usage count */
	kref_put(&dev->kref, lcd_delete);
	kref_put(&dev->kref, lcd_delete);