USB: fix DoS in pwc USB video driver
authorOliver Neukum <oneukum@suse.de>
Tue, 21 Aug 2007 05:10:42 +0000 (07:10 +0200)
committerGreg Kroah-Hartman <gregkh@suse.de>
Wed, 22 Aug 2007 21:27:58 +0000 (14:27 -0700)
the pwc driver has a disconnect method that waits for user space to
close the device. This opens up an opportunity for a DoS attack,
blocking the USB subsystem and making khubd's task busy wait in
kernel space. This patch shifts freeing resources to close if an opened
device is disconnected.

Signed-off-by: Oliver Neukum <oneukum@suse.de>
CC: stable <stable@kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/media/video/pwc/pwc-if.c
drivers/media/video/pwc/pwc.h

index 9c0e8d18c2f6085e7bcb9a39c53dcb6212283179..3d81966d8c421eda17a483059dae48bb0f443471 100644 (file)
@@ -1196,12 +1196,19 @@ static int pwc_video_open(struct inode *inode, struct file *file)
        return 0;
 }
 
+
+static void pwc_cleanup(struct pwc_device *pdev)
+{
+       pwc_remove_sysfs_files(pdev->vdev);
+       video_unregister_device(pdev->vdev);
+}
+
 /* Note that all cleanup is done in the reverse order as in _open */
 static int pwc_video_close(struct inode *inode, struct file *file)
 {
        struct video_device *vdev = file->private_data;
        struct pwc_device *pdev;
-       int i;
+       int i, hint;
 
        PWC_DEBUG_OPEN(">> video_close called(vdev = 0x%p).\n", vdev);
 
@@ -1224,8 +1231,9 @@ static int pwc_video_close(struct inode *inode, struct file *file)
        pwc_isoc_cleanup(pdev);
        pwc_free_buffers(pdev);
 
+       lock_kernel();
        /* Turn off LEDS and power down camera, but only when not unplugged */
-       if (pdev->error_status != EPIPE) {
+       if (!pdev->unplugged) {
                /* Turn LEDs off */
                if (pwc_set_leds(pdev, 0, 0) < 0)
                        PWC_DEBUG_MODULE("Failed to set LED on/off time.\n");
@@ -1234,9 +1242,19 @@ static int pwc_video_close(struct inode *inode, struct file *file)
                        if (i < 0)
                                PWC_ERROR("Failed to power down camera (%d)\n", i);
                }
+               pdev->vopen--;
+               PWC_DEBUG_OPEN("<< video_close() vopen=%d\n", i);
+       } else {
+               pwc_cleanup(pdev);
+               /* Free memory (don't set pdev to 0 just yet) */
+               kfree(pdev);
+               /* search device_hint[] table if we occupy a slot, by any chance */
+               for (hint = 0; hint < MAX_DEV_HINTS; hint++)
+                       if (device_hint[hint].pdev == pdev)
+                               device_hint[hint].pdev = NULL;
        }
-       pdev->vopen--;
-       PWC_DEBUG_OPEN("<< video_close() vopen=%d\n", pdev->vopen);
+       unlock_kernel();
+
        return 0;
 }
 
@@ -1791,21 +1809,21 @@ static void usb_pwc_disconnect(struct usb_interface *intf)
        /* Alert waiting processes */
        wake_up_interruptible(&pdev->frameq);
        /* Wait until device is closed */
-       while (pdev->vopen)
-               schedule();
-       /* Device is now closed, so we can safely unregister it */
-       PWC_DEBUG_PROBE("Unregistering video device in disconnect().\n");
-       pwc_remove_sysfs_files(pdev->vdev);
-       video_unregister_device(pdev->vdev);
-
-       /* Free memory (don't set pdev to 0 just yet) */
-       kfree(pdev);
+       if(pdev->vopen) {
+               pdev->unplugged = 1;
+       } else {
+               /* Device is closed, so we can safely unregister it */
+               PWC_DEBUG_PROBE("Unregistering video device in disconnect().\n");
+               pwc_cleanup(pdev);
+               /* Free memory (don't set pdev to 0 just yet) */
+               kfree(pdev);
 
 disconnect_out:
-       /* search device_hint[] table if we occupy a slot, by any chance */
-       for (hint = 0; hint < MAX_DEV_HINTS; hint++)
-               if (device_hint[hint].pdev == pdev)
-                       device_hint[hint].pdev = NULL;
+               /* search device_hint[] table if we occupy a slot, by any chance */
+               for (hint = 0; hint < MAX_DEV_HINTS; hint++)
+                       if (device_hint[hint].pdev == pdev)
+                               device_hint[hint].pdev = NULL;
+       }
 
        unlock_kernel();
 }
index 910a04f539202a2d6a5f3b2adb879c7c551a69b1..8e8e5b27e77ea2f9c77edd549bc696b7b7c1000e 100644 (file)
@@ -193,6 +193,7 @@ struct pwc_device
    char vsnapshot;             /* snapshot mode */
    char vsync;                 /* used by isoc handler */
    char vmirror;               /* for ToUCaM series */
+       char unplugged;
 
    int cmd_len;
    unsigned char cmd_buf[13];