[BUG] Race condition between open and disconnect

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]


Hi everybody,

I'm afraid to report that the move to the cdev interface in 2.6.28 has 
introduced a race condition between open and disconnect.

To avoid the need of a reference count in every v4l2 driver, v4l2 moved to 
cdev which includes its own reference counting infrastructure based on 
kobject.

kobject_(get|put) calls on which the reference counting code relies 
(kobject_get being called by cdev_get in chrdev_open, and kobject_put being 
called by device_unregister in video_device_unregister) call use the embedded 
kref_(get|put). Unfortunately, while those calls use atomic operations to 
access the reference counter, they are not thread-safe. A call to kref_get 
can succeed even though a call to kref_put has released the last reference.

I've modified the UVC driver to remove the thread-safe reference counting that 
was supposed not to be required anymore with the move to cdev and added an 
msleep(3000) at the beginning of v4l2_chardev_release to make the race 
condition easier to reproduce (patch against my hg tree attached).

Steps to reproduce:

- load the uvcvideo module
- connect a UVC camera
- start any video application
- close the application
- disconnect the camera
- within 3 seconds of the disconnection, start the video application
- check dmesg for the bug report and enjoy


usb 2-1: USB disconnect, address 6
------------[ cut here ]------------
WARNING: at lib/kref.c:43 kref_get+0x1c/0x20()
Modules linked in: snd_usb_audio snd_usb_lib snd_rawmidi snd_hwdep uvcvideo 
videodev v4l2_compat_ioctl32 v4l1_compat radeon drm arc4 ecb 
ieee80211_crypt_tkip rfcomm l2cap hci_usb ipw2200 bluetooth ieee80211 r8169 
snd_intel8x0m ieee80211_crypt
Pid: 19247, comm: luvcview Not tainted 2.6.27 #36
 [<c011cd6f>] warn_on_slowpath+0x5f/0x90
 [<c01192bf>] try_to_wake_up+0xaf/0xc0
 [<c01806e7>] __d_lookup+0xf7/0x150
 [<c01d70e0>] xattr_lookup_poison+0x0/0xa0
 [<c01762c5>] do_lookup+0x65/0x1a0
 [<c018005c>] dput+0x1c/0x160
 [<c0178331>] __link_path_walk+0xb01/0xc90
 [<c025e6ac>] kref_get+0x1c/0x20
 [<c025d8ef>] kobject_get+0xf/0x20
 [<c01710c2>] cdev_get+0x22/0x90
 [<c01717c7>] chrdev_open+0x37/0x1e0
 [<c0171790>] chrdev_open+0x0/0x1e0
 [<c016ce24>] __dentry_open+0xd4/0x260the 
 [<c016cff5>] nameidata_to_filp+0x45/0x60
 [<c0179617>] do_filp_open+0x187/0x720
 [<c02a2488>] tty_write+0x1b8/0x1e0
 [<c016cbfe>] do_sys_open+0x4e/0xe0
 [<c016cd0c>] sys_open+0x2c/0x40
 [<c0103119>] sysenter_do_call+0x12/0x21
 [<c0440000>] pfkey_add+0x4f0/0x7f0
 =======================
---[ end trace 32937b1bc9a02398 ]---
------------[ cut here ]------------
kernel BUG at drivers/media/video/v4l2-dev.c:119!
invalid opcode: 0000 [#1] PREEMPT
Modules linked in: snd_usb_audio snd_usb_lib snd_rawmidi snd_hwdep uvcvideo 
videodev v4l2_compat_ioctl32 v4l1_compat radeon drm arc4 ecb 
ieee80211_crypt_tkip rfcomm l2cap hci_usb ipw2200 bluetooth ieee80211 r8169 
snd_intel8x0m ieee80211_crypt

Pid: 19247, comm: luvcview Tainted: G        W (2.6.27 #36)
EIP: 0060:[<f09d14e7>] EFLAGS: 00010202 CPU: 0
EIP is at v4l2_chardev_release+0x37/0x80 [videodev]
EAX: f09d8a90 EBX: ed069c00 ECX: 00000003 EDX: 00000000
ESI: ed069d20 EDI: 00000000 EBP: d836968c ESP: e9005e6c
 DS: 007b ES: 007b FS: 0000 GS: 0033 SS: 0068
Process luvcview (pid: 19247, ti=e9004000 task=e90f31a0 task.ti=e9004000)
Stack: ed069d20 f09d8a9c c025d896 ed069d3c c025d860 ffffffed c025e659 f0c0f500
       00000001 c017121d ed069d20 c0171887 ed2ddb40 00000006 ed2ddb40 d836968c
       00000000 c0171790 c016ce24 ef80d1c0 d7861760 ed2ddb40 e9005f10 00000026
Call Trace:
 [<c025d896>] kobject_release+0x36/0x80
 [<c025d860>] kobject_release+0x0/0x80
 [<c025e659>] kref_put+0x29/0x60
 [<c017121d>] cdev_put+0xd/0x20
 [<c0171887>] chrdev_open+0xf7/0x1e0
 [<c0171790>] chrdev_open+0x0/0x1e0
 [<c016ce24>] __dentry_open+0xd4/0x260
 [<c016cff5>] nameidata_to_filp+0x45/0x60
 [<c0179617>] do_filp_open+0x187/0x720
 [<c02a2488>] tty_write+0x1b8/0x1e0
 [<c016cbfe>] do_sys_open+0x4e/0xe0
 [<c016cd0c>] sys_open+0x2c/0x40
 [<c0103119>] sysenter_do_call+0x12/0x21
 [<c0440000>] pfkey_add+0x4f0/0x7f0
 =======================
Code: 0b 00 00 e8 cc 47 75 cf b8 90 8a 9d f0 e8 a2 8b a7 cf 8b 83 88 01 00 00 
3b 1c 85 00 8d 9d f0 74 0e b8 90 8a 9d f0 e8 99 8b a7 cf <0f> 0b eb fe 31 c9 
89 0c 85 00 8d 9d f0 8b 83 84 01 00 00 0f b7
EIP: [<f09d14e7>] v4l2_chardev_release+0x37/0x80 [videodev] SS:ESP 
0068:e9005e6c
---[ end trace 32937b1bc9a02398 ]---

Best regards,

Laurent Pinchart
diff -r 0f0594461ca5 linux/drivers/media/video/uvc/uvc_driver.c
--- a/linux/drivers/media/video/uvc/uvc_driver.c	Sat Dec 06 21:43:40 2008 +0100
+++ b/linux/drivers/media/video/uvc/uvc_driver.c	Sun Dec 07 13:09:06 2008 +0100
@@ -1169,20 +1169,6 @@
  */
 
 /*
- * Unregister the video devices.
- */
-static void uvc_unregister_video(struct uvc_device *dev)
-{
-	if (dev->video.vdev) {
-		if (dev->video.vdev->minor == -1)
-			video_device_release(dev->video.vdev);
-		else
-			video_unregister_device(dev->video.vdev);
-		dev->video.vdev = NULL;
-	}
-}
-
-/*
  * Scan the UVC descriptors to locate a chain starting at an Output Terminal
  * and containing the following units:
  *
@@ -1398,6 +1384,64 @@
 }
 
 /*
+ * Delete the UVC device.
+ *
+ * Called by the kernel when the last reference to the V4L2 device (and thus
+ * the uvc_device structure) is released.
+ *
+ * As this function is called after or during disconnect(), all URBs have
+ * already been canceled by the USB core. There is no need to kill the
+ * interrupt URB manually.
+ */
+static void uvc_delete(struct uvc_device *dev)
+{
+	struct list_head *p, *n;
+
+	usb_put_intf(dev->intf);
+	usb_put_dev(dev->udev);
+
+	uvc_status_cleanup(dev);
+	uvc_ctrl_cleanup_device(dev);
+
+	list_for_each_safe(p, n, &dev->entities) {
+		struct uvc_entity *entity;
+		entity = list_entry(p, struct uvc_entity, list);
+		kfree(entity);
+	}
+
+	list_for_each_safe(p, n, &dev->streaming) {
+		struct uvc_streaming *streaming;
+		streaming = list_entry(p, struct uvc_streaming, list);
+		usb_driver_release_interface(&uvc_driver.driver,
+			streaming->intf);
+		usb_put_intf(streaming->intf);
+		kfree(streaming->format);
+		kfree(streaming->header.bmaControls);
+		kfree(streaming);
+	}
+
+	kfree(dev);
+}
+
+static void uvc_release(struct video_device *vdev)
+{
+	struct uvc_video_device *video = video_get_drvdata(vdev);
+	struct uvc_device *dev = video->dev;
+
+	video_device_release(vdev);
+	uvc_delete(dev);
+}
+
+/*
+ * Unregister the video devices.
+ */
+static void uvc_unregister_video(struct uvc_device *dev)
+{
+	video_unregister_device(dev->video.vdev);
+	dev->video.vdev = NULL;
+}
+
+/*
  * Register the video devices.
  *
  * The driver currently supports a single video device per control interface
@@ -1480,7 +1524,7 @@
 	vdev->parent = &dev->intf->dev;
 	vdev->minor = -1;
 	vdev->fops = &uvc_fops;
-	vdev->release = video_device_release;
+	vdev->release = uvc_release;
 	strncpy(vdev->name, dev->name, sizeof vdev->name);
 
 	/* Set the driver data before calling video_register_device, otherwise
@@ -1500,55 +1544,6 @@
 	}
 
 	return 0;
-}
-
-/*
- * Delete the UVC device.
- *
- * Called by the kernel when the last reference to the uvc_device structure
- * is released.
- *
- * Unregistering the video devices is done here because every opened instance
- * must be closed before the device can be unregistered. An alternative would
- * have been to use another reference count for uvc_v4l2_open/uvc_release, and
- * unregister the video devices on disconnect when that reference count drops
- * to zero.
- *
- * As this function is called after or during disconnect(), all URBs have
- * already been canceled by the USB core. There is no need to kill the
- * interrupt URB manually.
- */
-void uvc_delete(struct kref *kref)
-{
-	struct uvc_device *dev = container_of(kref, struct uvc_device, kref);
-	struct list_head *p, *n;
-
-	/* Unregister the video device */
-	uvc_unregister_video(dev);
-	usb_put_intf(dev->intf);
-	usb_put_dev(dev->udev);
-
-	uvc_status_cleanup(dev);
-	uvc_ctrl_cleanup_device(dev);
-
-	list_for_each_safe(p, n, &dev->entities) {
-		struct uvc_entity *entity;
-		entity = list_entry(p, struct uvc_entity, list);
-		kfree(entity);
-	}
-
-	list_for_each_safe(p, n, &dev->streaming) {
-		struct uvc_streaming *streaming;
-		streaming = list_entry(p, struct uvc_streaming, list);
-		usb_driver_release_interface(&uvc_driver.driver,
-			streaming->intf);
-		usb_put_intf(streaming->intf);
-		kfree(streaming->format);
-		kfree(streaming->header.bmaControls);
-		kfree(streaming);
-	}
-
-	kfree(dev);
 }
 
 static int uvc_probe(struct usb_interface *intf,
@@ -1572,7 +1567,6 @@
 
 	INIT_LIST_HEAD(&dev->entities);
 	INIT_LIST_HEAD(&dev->streaming);
-	kref_init(&dev->kref);
 
 	dev->udev = usb_get_dev(udev);
 	dev->intf = usb_get_intf(intf);
@@ -1629,7 +1623,7 @@
 	return 0;
 
 error:
-	kref_put(&dev->kref, uvc_delete);
+	uvc_delete(dev);
 	return -ENODEV;
 }
 
@@ -1645,21 +1639,9 @@
 	if (intf->cur_altsetting->desc.bInterfaceSubClass == SC_VIDEOSTREAMING)
 		return;
 
-	/* uvc_v4l2_open() might race uvc_disconnect(). A static driver-wide
-	 * lock is needed to prevent uvc_disconnect from releasing its
-	 * reference to the uvc_device instance after uvc_v4l2_open() received
-	 * the pointer to the device (video_devdata) but before it got the
-	 * chance to increase the reference count (kref_get).
-	 *
-	 * Note that the reference can't be released with the lock held,
-	 * otherwise a AB-BA deadlock can occur with videodev_lock that
-	 * videodev acquires in videodev_open() and video_unregister_device().
-	 */
-	mutex_lock(&uvc_driver.open_mutex);
 	dev->state |= UVC_DEV_DISCONNECTED;
-	mutex_unlock(&uvc_driver.open_mutex);
 
-	kref_put(&dev->kref, uvc_delete);
+	uvc_unregister_video(dev);
 }
 
 static int uvc_suspend(struct usb_interface *intf, pm_message_t message)
diff -r 0f0594461ca5 linux/drivers/media/video/uvc/uvc_v4l2.c
--- a/linux/drivers/media/video/uvc/uvc_v4l2.c	Sat Dec 06 21:43:40 2008 +0100
+++ b/linux/drivers/media/video/uvc/uvc_v4l2.c	Sun Dec 07 13:09:06 2008 +0100
@@ -398,7 +398,6 @@
 	int ret = 0;
 
 	uvc_trace(UVC_TRACE_CALLS, "uvc_v4l2_open\n");
-	mutex_lock(&uvc_driver.open_mutex);
 	video = video_drvdata(file);
 
 	if (video->dev->state & UVC_DEV_DISCONNECTED) {
@@ -426,10 +425,7 @@
 	handle->state = UVC_HANDLE_PASSIVE;
 	file->private_data = handle;
 
-	kref_get(&video->dev->kref);
-
 done:
-	mutex_unlock(&uvc_driver.open_mutex);
 	return ret;
 }
 
@@ -459,7 +455,6 @@
 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)
 	usb_autopm_put_interface(video->dev->intf);
 #endif
-	kref_put(&video->dev->kref, uvc_delete);
 	return 0;
 }
 
diff -r 0f0594461ca5 linux/drivers/media/video/uvc/uvcvideo.h
--- a/linux/drivers/media/video/uvc/uvcvideo.h	Sat Dec 06 21:43:40 2008 +0100
+++ b/linux/drivers/media/video/uvc/uvcvideo.h	Sun Dec 07 13:09:06 2008 +0100
@@ -624,7 +624,6 @@
 	char name[32];
 
 	enum uvc_device_state state;
-	struct kref kref;
 	struct list_head list;
 
 	/* Video control interface */
@@ -718,7 +717,6 @@
 
 /* Core driver */
 extern struct uvc_driver uvc_driver;
-extern void uvc_delete(struct kref *kref);
 
 /* Video buffers queue management. */
 extern void uvc_queue_init(struct uvc_video_queue *queue);
diff -r 0f0594461ca5 linux/drivers/media/video/v4l2-dev.c
--- a/linux/drivers/media/video/v4l2-dev.c	Sat Dec 06 21:43:40 2008 +0100
+++ b/linux/drivers/media/video/v4l2-dev.c	Sun Dec 07 13:09:06 2008 +0100
@@ -107,10 +107,12 @@
 EXPORT_SYMBOL(video_device_release_empty);
 
 /* Called when the last user of the character device is gone. */
+#include <linux/delay.h>
 static void v4l2_chardev_release(struct kobject *kobj)
 {
 	struct video_device *vfd = container_of(kobj, struct video_device, cdev.kobj);
 
+	msleep(3000);
 	mutex_lock(&videodev_lock);
 	if (video_device[vfd->minor] != vfd) {
 		mutex_unlock(&videodev_lock);
--
video4linux-list mailing list
Unsubscribe mailto:video4linux-list-request@xxxxxxxxxx?subject=unsubscribe
https://www.redhat.com/mailman/listinfo/video4linux-list

[Linux Media]     [Older V4L]     [Linux DVB]     [Video Disk Recorder]     [Linux Kernel]     [Asterisk]     [Photo]     [DCCP]     [Netdev]     [Xorg]     [Util Linux NG]     [Xfree86]     [Free Photo Albums]     [Fedora Users]     [Fedora Women]     [ALSA Users]     [ALSA Devel]     [SSH]     [DVB Maintainers]     [Linux USB]     [Yosemite Information]

Add to Google Powered by Linux