| From 212a871a3934beccf43431608c27ed2e05a476ec Mon Sep 17 00:00:00 2001 |
| From: Manoj Chourasia <mchourasia@nvidia.com> |
| Date: Mon, 22 Jul 2013 15:33:13 +0530 |
| Subject: HID: hidraw: correctly deallocate memory on device disconnect |
| |
| From: Manoj Chourasia <mchourasia@nvidia.com> |
| |
| commit 212a871a3934beccf43431608c27ed2e05a476ec upstream. |
| |
| This changes puts the commit 4fe9f8e203f back in place |
| with the fixes for slab corruption because of the commit. |
| |
| When a device is unplugged, wait for all processes that |
| have opened the device to close before deallocating the device. |
| |
| This commit was solving kernel crash because of the corruption in |
| rb tree of vmalloc. The rootcause was the device data pointer was |
| geting excessed after the memory associated with hidraw was freed. |
| |
| The commit 4fe9f8e203f was buggy as it was also freeing the hidraw |
| first and then calling delete operation on the list associated with |
| that hidraw leading to slab corruption. |
| |
| Signed-off-by: Manoj Chourasia <mchourasia@nvidia.com> |
| Tested-by: Peter Wu <lekensteyn@gmail.com> |
| Signed-off-by: Jiri Kosina <jkosina@suse.cz> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| drivers/hid/hidraw.c | 60 +++++++++++++++++++++------------------------------ |
| 1 file changed, 25 insertions(+), 35 deletions(-) |
| |
| --- a/drivers/hid/hidraw.c |
| +++ b/drivers/hid/hidraw.c |
| @@ -113,7 +113,7 @@ static ssize_t hidraw_send_report(struct |
| __u8 *buf; |
| int ret = 0; |
| |
| - if (!hidraw_table[minor]) { |
| + if (!hidraw_table[minor] || !hidraw_table[minor]->exist) { |
| ret = -ENODEV; |
| goto out; |
| } |
| @@ -261,7 +261,7 @@ static int hidraw_open(struct inode *ino |
| } |
| |
| mutex_lock(&minors_lock); |
| - if (!hidraw_table[minor]) { |
| + if (!hidraw_table[minor] || !hidraw_table[minor]->exist) { |
| err = -ENODEV; |
| goto out_unlock; |
| } |
| @@ -302,39 +302,38 @@ static int hidraw_fasync(int fd, struct |
| return fasync_helper(fd, file, on, &list->fasync); |
| } |
| |
| +static void drop_ref(struct hidraw *hidraw, int exists_bit) |
| +{ |
| + if (exists_bit) { |
| + hid_hw_close(hidraw->hid); |
| + hidraw->exist = 0; |
| + if (hidraw->open) |
| + wake_up_interruptible(&hidraw->wait); |
| + } else { |
| + --hidraw->open; |
| + } |
| + |
| + if (!hidraw->open && !hidraw->exist) { |
| + device_destroy(hidraw_class, MKDEV(hidraw_major, hidraw->minor)); |
| + hidraw_table[hidraw->minor] = NULL; |
| + kfree(hidraw); |
| + } |
| +} |
| + |
| static int hidraw_release(struct inode * inode, struct file * file) |
| { |
| unsigned int minor = iminor(inode); |
| - struct hidraw *dev; |
| struct hidraw_list *list = file->private_data; |
| - int ret; |
| - int i; |
| |
| mutex_lock(&minors_lock); |
| - if (!hidraw_table[minor]) { |
| - ret = -ENODEV; |
| - goto unlock; |
| - } |
| |
| list_del(&list->node); |
| - dev = hidraw_table[minor]; |
| - if (!--dev->open) { |
| - if (list->hidraw->exist) { |
| - hid_hw_power(dev->hid, PM_HINT_NORMAL); |
| - hid_hw_close(dev->hid); |
| - } else { |
| - kfree(list->hidraw); |
| - } |
| - } |
| - |
| - for (i = 0; i < HIDRAW_BUFFER_SIZE; ++i) |
| - kfree(list->buffer[i].value); |
| kfree(list); |
| - ret = 0; |
| -unlock: |
| - mutex_unlock(&minors_lock); |
| |
| - return ret; |
| + drop_ref(hidraw_table[minor], 0); |
| + |
| + mutex_unlock(&minors_lock); |
| + return 0; |
| } |
| |
| static long hidraw_ioctl(struct file *file, unsigned int cmd, |
| @@ -539,18 +538,9 @@ void hidraw_disconnect(struct hid_device |
| struct hidraw *hidraw = hid->hidraw; |
| |
| mutex_lock(&minors_lock); |
| - hidraw->exist = 0; |
| - |
| - device_destroy(hidraw_class, MKDEV(hidraw_major, hidraw->minor)); |
| |
| - hidraw_table[hidraw->minor] = NULL; |
| + drop_ref(hidraw, 1); |
| |
| - if (hidraw->open) { |
| - hid_hw_close(hid); |
| - wake_up_interruptible(&hidraw->wait); |
| - } else { |
| - kfree(hidraw); |
| - } |
| mutex_unlock(&minors_lock); |
| } |
| EXPORT_SYMBOL_GPL(hidraw_disconnect); |