diff options
author | Yonghua Zheng <younghua.zheng@gmail.com> | 2013-08-26 23:38:35 +0800 |
---|---|---|
committer | Jiri Kosina <jkosina@suse.cz> | 2013-08-26 21:40:24 +0200 |
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) unsigned int minor = iminor(inode); struct hidraw *dev; struct hidraw_list *list; + unsigned long flags; int err = 0; if (!(list = kzalloc(sizeof(struct hidraw_list), GFP_KERNEL))) { @@ -266,11 +267,6 @@ static int hidraw_open(struct inode *inode, struct file *file) goto out_unlock; } - list->hidraw = hidraw_table[minor]; - mutex_init(&list->read_mutex); - list_add_tail(&list->node, &hidraw_table[minor]->list); - file->private_data = list; - dev = hidraw_table[minor]; if (!dev->open++) { err = hid_hw_power(dev->hid, PM_HINT_FULLON); @@ -283,9 +279,16 @@ static int hidraw_open(struct inode *inode, struct file *file) if (err < 0) { hid_hw_power(dev->hid, PM_HINT_NORMAL); dev->open--; + goto out_unlock; } } + list->hidraw = hidraw_table[minor]; + mutex_init(&list->read_mutex); + spin_lock_irqsave(&hidraw_table[minor]->list_lock, flags); + list_add_tail(&list->node, &hidraw_table[minor]->list); + spin_unlock_irqrestore(&hidraw_table[minor]->list_lock, flags); + file->private_data = list; out_unlock: mutex_unlock(&minors_lock); out: @@ -324,10 +327,13 @@ static int hidraw_release(struct inode * inode, struct file * file) { unsigned int minor = iminor(inode); struct hidraw_list *list = file->private_data; + unsigned long flags; mutex_lock(&minors_lock); + spin_lock_irqsave(&hidraw_table[minor]->list_lock, flags); list_del(&list->node); + spin_unlock_irqrestore(&hidraw_table[minor]->list_lock, flags); kfree(list); drop_ref(hidraw_table[minor], 0); @@ -456,7 +462,9 @@ int hidraw_report_event(struct hid_device *hid, u8 *data, int len) struct hidraw *dev = hid->hidraw; struct hidraw_list *list; int ret = 0; + unsigned long flags; + spin_lock_irqsave(&dev->list_lock, flags); list_for_each_entry(list, &dev->list, node) { int new_head = (list->head + 1) & (HIDRAW_BUFFER_SIZE - 1); @@ -471,6 +479,7 @@ int hidraw_report_event(struct hid_device *hid, u8 *data, int len) list->head = new_head; kill_fasync(&list->fasync, SIGIO, POLL_IN); } + spin_unlock_irqrestore(&dev->list_lock, flags); wake_up_interruptible(&dev->wait); return ret; @@ -519,6 +528,7 @@ int hidraw_connect(struct hid_device *hid) mutex_unlock(&minors_lock); init_waitqueue_head(&dev->wait); + spin_lock_init(&dev->list_lock); INIT_LIST_HEAD(&dev->list); 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 { wait_queue_head_t wait; struct hid_device *hid; struct device *dev; + spinlock_t list_lock; struct list_head list; }; |