| From eac7d7fbfa465491bcb4e60a39d08cc62260bcf0 Mon Sep 17 00:00:00 2001 |
| From: Johan Hovold <johan@kernel.org> |
| Date: Thu, 26 Sep 2019 11:12:25 +0200 |
| Subject: [PATCH] USB: usblcd: fix I/O after disconnect |
| |
| commit eb7f5a490c5edfe8126f64bc58b9ba2edef0a425 upstream. |
| |
| 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: 1da177e4c3f4 ("Linux-2.6.12-rc2") |
| Cc: stable <stable@vger.kernel.org> # 7bbe990c989e |
| Signed-off-by: Johan Hovold <johan@kernel.org> |
| Link: https://lore.kernel.org/r/20190926091228.24634-7-johan@kernel.org |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| Signed-off-by: Paul Gortmaker <paul.gortmaker@windriver.com> |
| |
| diff --git a/drivers/usb/misc/usblcd.c b/drivers/usb/misc/usblcd.c |
| index 9ba4a4e68d91..aa982d3ca36b 100644 |
| --- a/drivers/usb/misc/usblcd.c |
| +++ b/drivers/usb/misc/usblcd.c |
| @@ -18,6 +18,7 @@ |
| #include <linux/slab.h> |
| #include <linux/errno.h> |
| #include <linux/mutex.h> |
| +#include <linux/rwsem.h> |
| #include <linux/uaccess.h> |
| #include <linux/usb.h> |
| |
| @@ -57,6 +58,8 @@ struct usb_lcd { |
| using up all RAM */ |
| struct usb_anchor submitted; /* URBs to wait for |
| before suspend */ |
| + struct rw_semaphore io_rwsem; |
| + unsigned long disconnected:1; |
| }; |
| #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; |
| |
| + 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 */ |
| retval = usb_bulk_msg(dev->udev, |
| usb_rcvbulkpipe(dev->udev, |
| @@ -158,6 +168,9 @@ static ssize_t lcd_read(struct file *file, char __user * buffer, |
| retval = bytes_read; |
| } |
| |
| +out_up_io: |
| + up_read(&dev->io_rwsem); |
| + |
| return retval; |
| } |
| |
| @@ -237,11 +250,18 @@ static ssize_t lcd_write(struct file *file, const char __user * user_buffer, |
| if (r < 0) |
| 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 */ |
| urb = usb_alloc_urb(0, GFP_KERNEL); |
| if (!urb) { |
| retval = -ENOMEM; |
| - goto err_no_buf; |
| + goto err_up_io; |
| } |
| |
| 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 */ |
| usb_free_urb(urb); |
| |
| + up_read(&dev->io_rwsem); |
| exit: |
| return count; |
| error_unanchor: |
| @@ -285,7 +306,8 @@ static ssize_t lcd_write(struct file *file, const char __user * user_buffer, |
| error: |
| usb_free_coherent(dev->udev, count, buf, urb->transfer_dma); |
| usb_free_urb(urb); |
| -err_no_buf: |
| +err_up_io: |
| + up_read(&dev->io_rwsem); |
| up(&dev->limit_sem); |
| return retval; |
| } |
| @@ -325,6 +347,7 @@ static int lcd_probe(struct usb_interface *interface, |
| |
| kref_init(&dev->kref); |
| sema_init(&dev->limit_sem, USB_LCD_CONCURRENT_WRITES); |
| + init_rwsem(&dev->io_rwsem); |
| init_usb_anchor(&dev->submitted); |
| |
| 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 */ |
| 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 */ |
| kref_put(&dev->kref, lcd_delete); |
| |
| -- |
| 2.7.4 |
| |