Commit 38a3d44d authored by Adam Kropelin's avatar Adam Kropelin Committed by Linus Torvalds

[PATCH] input: Fix hiddev disconnect-while-in-use oops

hid-core calls hiddev_disconnect() when the underlying device goes away
(hot unplug or system shutdown).  Normally, hiddev_disconnect() will clean
up nicely and return to hid-core who then frees the hid structure.
However, if the corresponding hiddev node is open at disconnect time,
hiddev delays the majority of disconnect work until the device is closed
via hiddev_release().  hiddev_release() calls hiddev_cleanup() which
proceeds to dereference the hid struct which hid-core freed back when the
hardware was disconnected.  Oops.

To solve this, we change hiddev_disconnect() to deregister the hiddev minor
and invalidate its table entry immediately and delay only the freeing of
the hiddev structure itself.  We're protected against future operations on
the fd since the major fops check hiddev->exists.
Signed-off-by: default avatarAdam Kropelin <akropel1@rochester.rr.com>
Signed-off-by: default avatarVojtech Pavlik <vojtech@suse.cz>
Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent 0ed7d2e4
...@@ -223,16 +223,6 @@ static int hiddev_fasync(int fd, struct file *file, int on) ...@@ -223,16 +223,6 @@ static int hiddev_fasync(int fd, struct file *file, int on)
return retval < 0 ? retval : 0; return retval < 0 ? retval : 0;
} }
/*
* De-allocate a hiddev structure
*/
static struct usb_class_driver hiddev_class;
static void hiddev_cleanup(struct hiddev *hiddev)
{
hiddev_table[hiddev->hid->minor - HIDDEV_MINOR_BASE] = NULL;
usb_deregister_dev(hiddev->hid->intf, &hiddev_class);
kfree(hiddev);
}
/* /*
* release file op * release file op
...@@ -253,7 +243,7 @@ static int hiddev_release(struct inode * inode, struct file * file) ...@@ -253,7 +243,7 @@ static int hiddev_release(struct inode * inode, struct file * file)
if (list->hiddev->exist) if (list->hiddev->exist)
hid_close(list->hiddev->hid); hid_close(list->hiddev->hid);
else else
hiddev_cleanup(list->hiddev); kfree(list->hiddev);
} }
kfree(list); kfree(list);
...@@ -795,17 +785,21 @@ int hiddev_connect(struct hid_device *hid) ...@@ -795,17 +785,21 @@ int hiddev_connect(struct hid_device *hid)
* This is where hid.c calls us to disconnect a hiddev device from the * This is where hid.c calls us to disconnect a hiddev device from the
* corresponding hid device (usually because the usb device has disconnected) * corresponding hid device (usually because the usb device has disconnected)
*/ */
static struct usb_class_driver hiddev_class;
void hiddev_disconnect(struct hid_device *hid) void hiddev_disconnect(struct hid_device *hid)
{ {
struct hiddev *hiddev = hid->hiddev; struct hiddev *hiddev = hid->hiddev;
hiddev->exist = 0; hiddev->exist = 0;
hiddev_table[hiddev->hid->minor - HIDDEV_MINOR_BASE] = NULL;
usb_deregister_dev(hiddev->hid->intf, &hiddev_class);
if (hiddev->open) { if (hiddev->open) {
hid_close(hiddev->hid); hid_close(hiddev->hid);
wake_up_interruptible(&hiddev->wait); wake_up_interruptible(&hiddev->wait);
} else { } else {
hiddev_cleanup(hiddev); kfree(hiddev);
} }
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment