diff options
author | Jiri Kosina <jkosina@suse.cz> | 2011-05-20 04:50:13 -0400 |
---|---|---|
committer | Jiri Kosina <jkosina@suse.cz> | 2011-05-20 04:50:13 -0400 |
commit | 6cb4b040795c555c7ab4b1ba29b0dba2b5a42beb (patch) | |
tree | ddc3db5bb24691ecd5da6bbc4dec044d2a4a5e78 /drivers/hid/usbhid/hiddev.c | |
parent | 437f3b199c437e2a9ac01b9ab733c78e5fc7c720 (diff) |
HID: hiddev: fix race between hiddev_disconnect and hiddev_release
When hiddev_disconnect() runs with chardev open, it will proceed with
usbhid_close(). When userspace in parallel runs the hiddev_release(),
it sees !hiddev->exists (as it has been already set so by
hiddev_disconnect()) and kfrees hiddev while hiddev_disconnect() hasn't
finished yet.
Serialize the access to hiddev->exists and hiddev->open by existancelock.
Reported-by: mike-@cinci.rr.com
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
Diffstat (limited to 'drivers/hid/usbhid/hiddev.c')
-rw-r--r-- | drivers/hid/usbhid/hiddev.c | 10 |
1 files changed, 8 insertions, 2 deletions
diff --git a/drivers/hid/usbhid/hiddev.c b/drivers/hid/usbhid/hiddev.c index 2baa71e6cc29..f4c67a5021c7 100644 --- a/drivers/hid/usbhid/hiddev.c +++ b/drivers/hid/usbhid/hiddev.c | |||
@@ -242,6 +242,7 @@ static int hiddev_release(struct inode * inode, struct file * file) | |||
242 | list_del(&list->node); | 242 | list_del(&list->node); |
243 | spin_unlock_irqrestore(&list->hiddev->list_lock, flags); | 243 | spin_unlock_irqrestore(&list->hiddev->list_lock, flags); |
244 | 244 | ||
245 | mutex_lock(&list->hiddev->existancelock); | ||
245 | if (!--list->hiddev->open) { | 246 | if (!--list->hiddev->open) { |
246 | if (list->hiddev->exist) { | 247 | if (list->hiddev->exist) { |
247 | usbhid_close(list->hiddev->hid); | 248 | usbhid_close(list->hiddev->hid); |
@@ -252,6 +253,7 @@ static int hiddev_release(struct inode * inode, struct file * file) | |||
252 | } | 253 | } |
253 | 254 | ||
254 | kfree(list); | 255 | kfree(list); |
256 | mutex_unlock(&list->hiddev->existancelock); | ||
255 | 257 | ||
256 | return 0; | 258 | return 0; |
257 | } | 259 | } |
@@ -300,17 +302,21 @@ static int hiddev_open(struct inode *inode, struct file *file) | |||
300 | list_add_tail(&list->node, &hiddev->list); | 302 | list_add_tail(&list->node, &hiddev->list); |
301 | spin_unlock_irq(&list->hiddev->list_lock); | 303 | spin_unlock_irq(&list->hiddev->list_lock); |
302 | 304 | ||
305 | mutex_lock(&hiddev->existancelock); | ||
303 | if (!list->hiddev->open++) | 306 | if (!list->hiddev->open++) |
304 | if (list->hiddev->exist) { | 307 | if (list->hiddev->exist) { |
305 | struct hid_device *hid = hiddev->hid; | 308 | struct hid_device *hid = hiddev->hid; |
306 | res = usbhid_get_power(hid); | 309 | res = usbhid_get_power(hid); |
307 | if (res < 0) { | 310 | if (res < 0) { |
308 | res = -EIO; | 311 | res = -EIO; |
309 | goto bail; | 312 | goto bail_unlock; |
310 | } | 313 | } |
311 | usbhid_open(hid); | 314 | usbhid_open(hid); |
312 | } | 315 | } |
316 | mutex_unlock(&hiddev->existancelock); | ||
313 | return 0; | 317 | return 0; |
318 | bail_unlock: | ||
319 | mutex_unlock(&hiddev->existancelock); | ||
314 | bail: | 320 | bail: |
315 | file->private_data = NULL; | 321 | file->private_data = NULL; |
316 | kfree(list); | 322 | kfree(list); |
@@ -911,7 +917,6 @@ void hiddev_disconnect(struct hid_device *hid) | |||
911 | 917 | ||
912 | mutex_lock(&hiddev->existancelock); | 918 | mutex_lock(&hiddev->existancelock); |
913 | hiddev->exist = 0; | 919 | hiddev->exist = 0; |
914 | mutex_unlock(&hiddev->existancelock); | ||
915 | 920 | ||
916 | usb_deregister_dev(usbhid->intf, &hiddev_class); | 921 | usb_deregister_dev(usbhid->intf, &hiddev_class); |
917 | 922 | ||
@@ -921,4 +926,5 @@ void hiddev_disconnect(struct hid_device *hid) | |||
921 | } else { | 926 | } else { |
922 | kfree(hiddev); | 927 | kfree(hiddev); |
923 | } | 928 | } |
929 | mutex_unlock(&hiddev->existancelock); | ||
924 | } | 930 | } |