summary refs log tree commit diff
path: root/drivers/usb/core/file.c
diff options
context:
space:
mode:
authorAlan Stern <stern@rowland.harvard.edu>2007-05-22 11:46:41 -0400
committerGreg Kroah-Hartman <gregkh@suse.de>2007-07-12 16:29:48 -0700
commitd4ead16f50f9ad30bdc7276ec8fee7a24c72f294 (patch)
treee1905abbc393cc4d73180dd7b9e1cf860378b590 /drivers/usb/core/file.c
parent55e5fdfa541ec7bf1b1613624ed4dd8cdacaa841 (diff)
downloadlinux-d4ead16f50f9ad30bdc7276ec8fee7a24c72f294.tar.gz
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>

Diffstat (limited to 'drivers/usb/core/file.c')
-rw-r--r--drivers/usb/core/file.c29
1 files changed, 13 insertions, 16 deletions
diff --git a/drivers/usb/core/file.c b/drivers/usb/core/file.c
index 01c857ac27af..5d860bc9b421 100644
--- a/drivers/usb/core/file.c
+++ b/drivers/usb/core/file.c
@@ -16,15 +16,15 @@
  */
 
 #include <linux/module.h>
-#include <linux/spinlock.h>
 #include <linux/errno.h>
+#include <linux/rwsem.h>
 #include <linux/usb.h>
 
 #include "usb.h"
 
 #define MAX_USB_MINORS	256
 static const struct file_operations *usb_minors[MAX_USB_MINORS];
-static DEFINE_SPINLOCK(minor_lock);
+static DECLARE_RWSEM(minor_rwsem);
 
 static int usb_open(struct inode * inode, struct file * file)
 {
@@ -33,14 +33,11 @@ static int usb_open(struct inode * inode, struct file * file)
 	int err = -ENODEV;
 	const struct file_operations *old_fops, *new_fops = NULL;
 
-	spin_lock (&minor_lock);
+	down_read(&minor_rwsem);
 	c = usb_minors[minor];
 
-	if (!c || !(new_fops = fops_get(c))) {
-		spin_unlock(&minor_lock);
-		return err;
-	}
-	spin_unlock(&minor_lock);
+	if (!c || !(new_fops = fops_get(c)))
+		goto done;
 
 	old_fops = file->f_op;
 	file->f_op = new_fops;
@@ -52,6 +49,8 @@ static int usb_open(struct inode * inode, struct file * file)
 		file->f_op = fops_get(old_fops);
 	}
 	fops_put(old_fops);
+ done:
+	up_read(&minor_rwsem);
 	return err;
 }
 
@@ -166,7 +165,7 @@ int usb_register_dev(struct usb_interface *intf,
 	if (class_driver->fops == NULL)
 		goto exit;
 
-	spin_lock (&minor_lock);
+	down_write(&minor_rwsem);
 	for (minor = minor_base; minor < MAX_USB_MINORS; ++minor) {
 		if (usb_minors[minor])
 			continue;
@@ -176,7 +175,7 @@ int usb_register_dev(struct usb_interface *intf,
 		retval = 0;
 		break;
 	}
-	spin_unlock (&minor_lock);
+	up_write(&minor_rwsem);
 
 	if (retval)
 		goto exit;
@@ -197,9 +196,9 @@ int usb_register_dev(struct usb_interface *intf,
 	intf->usb_dev = device_create(usb_class->class, &intf->dev,
 				      MKDEV(USB_MAJOR, minor), "%s", temp);
 	if (IS_ERR(intf->usb_dev)) {
-		spin_lock (&minor_lock);
+		down_write(&minor_rwsem);
 		usb_minors[intf->minor] = NULL;
-		spin_unlock (&minor_lock);
+		up_write(&minor_rwsem);
 		retval = PTR_ERR(intf->usb_dev);
 	}
 exit:
@@ -236,9 +235,9 @@ void usb_deregister_dev(struct usb_interface *intf,
 
 	dbg ("removing %d minor", intf->minor);
 
-	spin_lock (&minor_lock);
+	down_write(&minor_rwsem);
 	usb_minors[intf->minor] = NULL;
-	spin_unlock (&minor_lock);
+	up_write(&minor_rwsem);
 
 	snprintf(name, BUS_ID_SIZE, class_driver->name, intf->minor - minor_base);
 	device_destroy(usb_class->class, MKDEV(USB_MAJOR, intf->minor));
@@ -247,5 +246,3 @@ void usb_deregister_dev(struct usb_interface *intf,
 	destroy_usb_class();
 }
 EXPORT_SYMBOL(usb_deregister_dev);
-
-