USB: prevent char device open/deregister race
This patch (as908) adds central protection in usbcore for the
prototypical race between opening and unregistering a char device.
The spinlock used to protect the minor-numbers array is replaced with
an rwsem, which can remain locked across a call to a driver's open()
method. This guarantees that open() and deregister() will be mutually
exclusive.
The private locks currently used in several individual drivers for
this purpose are no longer necessary, and the patch removes them. The
following USB drivers are affected: usblcd, idmouse, auerswald,
legousbtower, sisusbvga/sisusb, ldusb, adutux, iowarrior, and
usb-skeleton.
As a side effect of this change, usb_deregister_dev() must not be
called while holding a lock that is acquired by open(). Unfortunately
a number of drivers do this, but luckily the solution is simple: call
usb_deregister_dev() before acquiring the lock.
In addition to these changes (and their consequent code
simplifications), the patch fixes a use-after-free bug in adutux and a
race between open() and release() in iowarrior.
Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
diff --git a/drivers/usb/misc/adutux.c b/drivers/usb/misc/adutux.c
index 77145f9..d72c42e 100644
--- a/drivers/usb/misc/adutux.c
+++ b/drivers/usb/misc/adutux.c
@@ -108,8 +108,6 @@
struct urb* interrupt_out_urb;
};
-/* prevent races between open() and disconnect */
-static DEFINE_MUTEX(disconnect_mutex);
static struct usb_driver adu_driver;
static void adu_debug_data(int level, const char *function, int size,
@@ -256,8 +254,6 @@
subminor = iminor(inode);
- mutex_lock(&disconnect_mutex);
-
interface = usb_find_interface(&adu_driver, subminor);
if (!interface) {
err("%s - error, can't find device for minor %d",
@@ -306,7 +302,6 @@
up(&dev->sem);
exit_no_device:
- mutex_unlock(&disconnect_mutex);
dbg(2,"%s : leave, return value %d ", __FUNCTION__, retval);
return retval;
@@ -318,12 +313,6 @@
dbg(2," %s : enter", __FUNCTION__);
- if (dev->udev == NULL) {
- /* the device was unplugged before the file was released */
- adu_delete(dev);
- goto exit;
- }
-
/* decrement our usage count for the device */
--dev->open_count;
dbg(2," %s : open count %d", __FUNCTION__, dev->open_count);
@@ -332,7 +321,6 @@
dev->open_count = 0;
}
-exit:
dbg(2," %s : leave", __FUNCTION__);
return retval;
}
@@ -367,8 +355,15 @@
goto exit;
}
- /* do the work */
- retval = adu_release_internal(dev);
+ if (dev->udev == NULL) {
+ /* the device was unplugged before the file was released */
+ up(&dev->sem);
+ adu_delete(dev);
+ dev = NULL;
+ } else {
+ /* do the work */
+ retval = adu_release_internal(dev);
+ }
exit:
if (dev)
@@ -831,19 +826,17 @@
dbg(2," %s : enter", __FUNCTION__);
- mutex_lock(&disconnect_mutex); /* not interruptible */
-
dev = usb_get_intfdata(interface);
usb_set_intfdata(interface, NULL);
- down(&dev->sem); /* not interruptible */
-
minor = dev->minor;
/* give back our minor */
usb_deregister_dev(interface, &adu_class);
dev->minor = 0;
+ down(&dev->sem); /* not interruptible */
+
/* if the device is not opened, then we clean up right now */
dbg(2," %s : open count %d", __FUNCTION__, dev->open_count);
if (!dev->open_count) {
@@ -854,8 +847,6 @@
up(&dev->sem);
}
- mutex_unlock(&disconnect_mutex);
-
dev_info(&interface->dev, "ADU device adutux%d now disconnected",
(minor - ADU_MINOR_BASE));