diff options
author | Yonghua Zheng <younghua.zheng@gmail.com> | 2013-08-26 11:38:35 -0400 |
---|---|---|
committer | Jiri Kosina <jkosina@suse.cz> | 2013-08-26 15:40:24 -0400 |
commit | 277fe44dd862412ee034470ad1c13a79d24e533b (patch) | |
tree | 7333fed46331fc756cfde7dfd58a1127de97955b | |
parent | 06bb5219118fb098f4b0c7dcb484b28a52bf1c14 (diff) |
HID: hidraw: Add spinlock in struct hidraw to protect list
It is unsafe to call list_for_each_entry in hidraw_report_event to
traverse each hidraw_list node without a lock protection, the list
could be modified if someone calls hidraw_release and list_del to
remove itself from the list, this can cause hidraw_report_event
to touch a deleted list struct and panic.
To prevent this, introduce a spinlock in struct hidraw to protect
list from concurrent access.
Signed-off-by: Yonghua Zheng <younghua.zheng@gmail.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
-rw-r--r-- | drivers/hid/hidraw.c | 20 | ||||
-rw-r--r-- | include/linux/hidraw.h | 1 |
2 files changed, 16 insertions, 5 deletions
diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c index 612a655bc9f0..194a5660a389 100644 --- a/drivers/hid/hidraw.c +++ b/drivers/hid/hidraw.c | |||
@@ -253,6 +253,7 @@ static int hidraw_open(struct inode *inode, struct file *file) | |||
253 | unsigned int minor = iminor(inode); | 253 | unsigned int minor = iminor(inode); |
254 | struct hidraw *dev; | 254 | struct hidraw *dev; |
255 | struct hidraw_list *list; | 255 | struct hidraw_list *list; |
256 | unsigned long flags; | ||
256 | int err = 0; | 257 | int err = 0; |
257 | 258 | ||
258 | if (!(list = kzalloc(sizeof(struct hidraw_list), GFP_KERNEL))) { | 259 | if (!(list = kzalloc(sizeof(struct hidraw_list), GFP_KERNEL))) { |
@@ -266,11 +267,6 @@ static int hidraw_open(struct inode *inode, struct file *file) | |||
266 | goto out_unlock; | 267 | goto out_unlock; |
267 | } | 268 | } |
268 | 269 | ||
269 | list->hidraw = hidraw_table[minor]; | ||
270 | mutex_init(&list->read_mutex); | ||
271 | list_add_tail(&list->node, &hidraw_table[minor]->list); | ||
272 | file->private_data = list; | ||
273 | |||
274 | dev = hidraw_table[minor]; | 270 | dev = hidraw_table[minor]; |
275 | if (!dev->open++) { | 271 | if (!dev->open++) { |
276 | err = hid_hw_power(dev->hid, PM_HINT_FULLON); | 272 | err = hid_hw_power(dev->hid, PM_HINT_FULLON); |
@@ -283,9 +279,16 @@ static int hidraw_open(struct inode *inode, struct file *file) | |||
283 | if (err < 0) { | 279 | if (err < 0) { |
284 | hid_hw_power(dev->hid, PM_HINT_NORMAL); | 280 | hid_hw_power(dev->hid, PM_HINT_NORMAL); |
285 | dev->open--; | 281 | dev->open--; |
282 | goto out_unlock; | ||
286 | } | 283 | } |
287 | } | 284 | } |
288 | 285 | ||
286 | list->hidraw = hidraw_table[minor]; | ||
287 | mutex_init(&list->read_mutex); | ||
288 | spin_lock_irqsave(&hidraw_table[minor]->list_lock, flags); | ||
289 | list_add_tail(&list->node, &hidraw_table[minor]->list); | ||
290 | spin_unlock_irqrestore(&hidraw_table[minor]->list_lock, flags); | ||
291 | file->private_data = list; | ||
289 | out_unlock: | 292 | out_unlock: |
290 | mutex_unlock(&minors_lock); | 293 | mutex_unlock(&minors_lock); |
291 | out: | 294 | out: |
@@ -324,10 +327,13 @@ static int hidraw_release(struct inode * inode, struct file * file) | |||
324 | { | 327 | { |
325 | unsigned int minor = iminor(inode); | 328 | unsigned int minor = iminor(inode); |
326 | struct hidraw_list *list = file->private_data; | 329 | struct hidraw_list *list = file->private_data; |
330 | unsigned long flags; | ||
327 | 331 | ||
328 | mutex_lock(&minors_lock); | 332 | mutex_lock(&minors_lock); |
329 | 333 | ||
334 | spin_lock_irqsave(&hidraw_table[minor]->list_lock, flags); | ||
330 | list_del(&list->node); | 335 | list_del(&list->node); |
336 | spin_unlock_irqrestore(&hidraw_table[minor]->list_lock, flags); | ||
331 | kfree(list); | 337 | kfree(list); |
332 | 338 | ||
333 | drop_ref(hidraw_table[minor], 0); | 339 | drop_ref(hidraw_table[minor], 0); |
@@ -456,7 +462,9 @@ int hidraw_report_event(struct hid_device *hid, u8 *data, int len) | |||
456 | struct hidraw *dev = hid->hidraw; | 462 | struct hidraw *dev = hid->hidraw; |
457 | struct hidraw_list *list; | 463 | struct hidraw_list *list; |
458 | int ret = 0; | 464 | int ret = 0; |
465 | unsigned long flags; | ||
459 | 466 | ||
467 | spin_lock_irqsave(&dev->list_lock, flags); | ||
460 | list_for_each_entry(list, &dev->list, node) { | 468 | list_for_each_entry(list, &dev->list, node) { |
461 | int new_head = (list->head + 1) & (HIDRAW_BUFFER_SIZE - 1); | 469 | int new_head = (list->head + 1) & (HIDRAW_BUFFER_SIZE - 1); |
462 | 470 | ||
@@ -471,6 +479,7 @@ int hidraw_report_event(struct hid_device *hid, u8 *data, int len) | |||
471 | list->head = new_head; | 479 | list->head = new_head; |
472 | kill_fasync(&list->fasync, SIGIO, POLL_IN); | 480 | kill_fasync(&list->fasync, SIGIO, POLL_IN); |
473 | } | 481 | } |
482 | spin_unlock_irqrestore(&dev->list_lock, flags); | ||
474 | 483 | ||
475 | wake_up_interruptible(&dev->wait); | 484 | wake_up_interruptible(&dev->wait); |
476 | return ret; | 485 | return ret; |
@@ -519,6 +528,7 @@ int hidraw_connect(struct hid_device *hid) | |||
519 | 528 | ||
520 | mutex_unlock(&minors_lock); | 529 | mutex_unlock(&minors_lock); |
521 | init_waitqueue_head(&dev->wait); | 530 | init_waitqueue_head(&dev->wait); |
531 | spin_lock_init(&dev->list_lock); | ||
522 | INIT_LIST_HEAD(&dev->list); | 532 | INIT_LIST_HEAD(&dev->list); |
523 | 533 | ||
524 | dev->hid = hid; | 534 | dev->hid = hid; |
diff --git a/include/linux/hidraw.h b/include/linux/hidraw.h index 2451662c728a..ddf52612eed8 100644 --- a/include/linux/hidraw.h +++ b/include/linux/hidraw.h | |||
@@ -23,6 +23,7 @@ struct hidraw { | |||
23 | wait_queue_head_t wait; | 23 | wait_queue_head_t wait; |
24 | struct hid_device *hid; | 24 | struct hid_device *hid; |
25 | struct device *dev; | 25 | struct device *dev; |
26 | spinlock_t list_lock; | ||
26 | struct list_head list; | 27 | struct list_head list; |
27 | }; | 28 | }; |
28 | 29 | ||