Commit 62c05ed6 authored by Johan Hovold's avatar Johan Hovold Committed by Khalid Elmously

USB: usblcd: fix I/O after disconnect

BugLink: https://bugs.launchpad.net/bugs/1848780

commit eb7f5a49 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: 1da177e4 ("Linux-2.6.12-rc2")
Cc: stable <stable@vger.kernel.org>	# 7bbe990cSigned-off-by: default avatarJohan Hovold <johan@kernel.org>
Link: https://lore.kernel.org/r/20190926091228.24634-7-johan@kernel.orgSigned-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: default avatarConnor Kuehl <connor.kuehl@canonical.com>
Signed-off-by: default avatarKhalid Elmously <khalid.elmously@canonical.com>
parent 6461dc6a
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,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>
...@@ -56,6 +57,8 @@ struct usb_lcd { ...@@ -56,6 +57,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)
...@@ -141,6 +144,13 @@ static ssize_t lcd_read(struct file *file, char __user * buffer, ...@@ -141,6 +144,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,
...@@ -157,6 +167,9 @@ static ssize_t lcd_read(struct file *file, char __user * buffer, ...@@ -157,6 +167,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;
} }
...@@ -236,11 +249,18 @@ static ssize_t lcd_write(struct file *file, const char __user * user_buffer, ...@@ -236,11 +249,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,
...@@ -277,6 +297,7 @@ static ssize_t lcd_write(struct file *file, const char __user * user_buffer, ...@@ -277,6 +297,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:
...@@ -284,7 +305,8 @@ static ssize_t lcd_write(struct file *file, const char __user * user_buffer, ...@@ -284,7 +305,8 @@ static ssize_t lcd_write(struct file *file, const char __user * user_buffer,
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;
} }
...@@ -327,6 +349,7 @@ static int lcd_probe(struct usb_interface *interface, ...@@ -327,6 +349,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));
...@@ -437,6 +460,12 @@ static void lcd_disconnect(struct usb_interface *interface) ...@@ -437,6 +460,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);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment