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:1da177e4c3
("Linux-2.6.12-rc2") Cc: stable <stable@vger.kernel.org> #7bbe990c98
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>
This commit is contained in:
parent
976392650a
commit
eb7f5a490c
|
@ -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);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue