Commit 1deb9d34 authored by Jiri Kosina's avatar Jiri Kosina

HID: debug: fix RCU preemption issue

Commit 2353f2be ("HID: protect hid_debug_list") introduced mutex
locking around debug_list access to prevent SMP races when debugfs
nodes are being operated upon by multiple userspace processess.

mutex is not a proper synchronization primitive though, as the hid-debug
callbacks are being called from atomic contexts.

We also have to be careful about disabling IRQs when taking the lock
to prevent deadlock against IRQ handlers.

Benjamin reports this has also been reported in RH bugzilla as bug #958935.

 ===============================
 [ INFO: suspicious RCU usage. ]
 3.9.0+ #94 Not tainted
 -------------------------------
 include/linux/rcupdate.h:476 Illegal context switch in RCU read-side critical section!

 other info that might help us debug this:

 rcu_scheduler_active = 1, debug_locks = 0
 4 locks held by Xorg/5502:
  #0:  (&evdev->mutex){+.+...}, at: [<ffffffff81512c3d>] evdev_write+0x6d/0x160
  #1:  (&(&dev->event_lock)->rlock#2){-.-...}, at: [<ffffffff8150dd9b>] input_inject_event+0x5b/0x230
  #2:  (rcu_read_lock){.+.+..}, at: [<ffffffff8150dd82>] input_inject_event+0x42/0x230
  #3:  (&(&usbhid->lock)->rlock){-.....}, at: [<ffffffff81565289>] usb_hidinput_input_event+0x89/0x120

 stack backtrace:
 CPU: 0 PID: 5502 Comm: Xorg Not tainted 3.9.0+ #94
 Hardware name: Dell Inc. OptiPlex 390/0M5DCD, BIOS A09 07/24/2012
  0000000000000001 ffff8800689c7c38 ffffffff816f249f ffff8800689c7c68
  ffffffff810acb1d 0000000000000000 ffffffff81a03ac7 000000000000019d
  0000000000000000 ffff8800689c7c90 ffffffff8107cda7 0000000000000000
 Call Trace:
  [<ffffffff816f249f>] dump_stack+0x19/0x1b
  [<ffffffff810acb1d>] lockdep_rcu_suspicious+0xfd/0x130
  [<ffffffff8107cda7>] __might_sleep+0xc7/0x230
  [<ffffffff816f7770>] mutex_lock_nested+0x40/0x3a0
  [<ffffffff81312ac4>] ? vsnprintf+0x354/0x640
  [<ffffffff81553cc4>] hid_debug_event+0x34/0x100
  [<ffffffff81554197>] hid_dump_input+0x67/0xa0
  [<ffffffff81556430>] hid_set_field+0x50/0x120
  [<ffffffff8156529a>] usb_hidinput_input_event+0x9a/0x120
  [<ffffffff8150d89e>] input_handle_event+0x8e/0x530
  [<ffffffff8150df10>] input_inject_event+0x1d0/0x230
  [<ffffffff8150dd82>] ? input_inject_event+0x42/0x230
  [<ffffffff81512cae>] evdev_write+0xde/0x160
  [<ffffffff81185038>] vfs_write+0xc8/0x1f0
  [<ffffffff81185535>] SyS_write+0x55/0xa0
  [<ffffffff81704482>] system_call_fastpath+0x16/0x1b
 BUG: sleeping function called from invalid context at kernel/mutex.c:413
 in_atomic(): 1, irqs_disabled(): 1, pid: 5502, name: Xorg
 INFO: lockdep is turned off.
 irq event stamp: 1098574
 hardirqs last  enabled at (1098573): [<ffffffff816fb53f>] _raw_spin_unlock_irqrestore+0x3f/0x70
 hardirqs last disabled at (1098574): [<ffffffff816faaf5>] _raw_spin_lock_irqsave+0x25/0xa0
 softirqs last  enabled at (1098306): [<ffffffff8104971f>] __do_softirq+0x18f/0x3c0
 softirqs last disabled at (1097867): [<ffffffff81049ad5>] irq_exit+0xa5/0xb0
 CPU: 0 PID: 5502 Comm: Xorg Not tainted 3.9.0+ #94
 Hardware name: Dell Inc. OptiPlex 390/0M5DCD, BIOS A09 07/24/2012
  ffffffff81a03ac7 ffff8800689c7c68 ffffffff816f249f ffff8800689c7c90
  ffffffff8107ce60 0000000000000000 ffff8800689c7fd8 ffff88006a62c800
  ffff8800689c7d10 ffffffff816f7770 ffff8800689c7d00 ffffffff81312ac4
 Call Trace:
  [<ffffffff816f249f>] dump_stack+0x19/0x1b
  [<ffffffff8107ce60>] __might_sleep+0x180/0x230
  [<ffffffff816f7770>] mutex_lock_nested+0x40/0x3a0
  [<ffffffff81312ac4>] ? vsnprintf+0x354/0x640
  [<ffffffff81553cc4>] hid_debug_event+0x34/0x100
  [<ffffffff81554197>] hid_dump_input+0x67/0xa0
  [<ffffffff81556430>] hid_set_field+0x50/0x120
  [<ffffffff8156529a>] usb_hidinput_input_event+0x9a/0x120
  [<ffffffff8150d89e>] input_handle_event+0x8e/0x530
  [<ffffffff8150df10>] input_inject_event+0x1d0/0x230
  [<ffffffff8150dd82>] ? input_inject_event+0x42/0x230
  [<ffffffff81512cae>] evdev_write+0xde/0x160
  [<ffffffff81185038>] vfs_write+0xc8/0x1f0
  [<ffffffff81185535>] SyS_write+0x55/0xa0
  [<ffffffff81704482>] system_call_fastpath+0x16/0x1b
Reported-by: default avatarmajianpeng <majianpeng@gmail.com>
Reported-by: default avatarBenjamin Tissoires <benjamin.tissoires@gmail.com>
Reviewed-by: default avatarDmitry Torokhov <dmitry.torokhov@gmail.com>
Signed-off-by: default avatarJiri Kosina <jkosina@suse.cz>
parent b52b5061
...@@ -2342,7 +2342,7 @@ struct hid_device *hid_allocate_device(void) ...@@ -2342,7 +2342,7 @@ struct hid_device *hid_allocate_device(void)
init_waitqueue_head(&hdev->debug_wait); init_waitqueue_head(&hdev->debug_wait);
INIT_LIST_HEAD(&hdev->debug_list); INIT_LIST_HEAD(&hdev->debug_list);
mutex_init(&hdev->debug_list_lock); spin_lock_init(&hdev->debug_list_lock);
sema_init(&hdev->driver_lock, 1); sema_init(&hdev->driver_lock, 1);
sema_init(&hdev->driver_input_lock, 1); sema_init(&hdev->driver_input_lock, 1);
......
...@@ -579,15 +579,16 @@ void hid_debug_event(struct hid_device *hdev, char *buf) ...@@ -579,15 +579,16 @@ void hid_debug_event(struct hid_device *hdev, char *buf)
{ {
int i; int i;
struct hid_debug_list *list; struct hid_debug_list *list;
unsigned long flags;
mutex_lock(&hdev->debug_list_lock); spin_lock_irqsave(&hdev->debug_list_lock, flags);
list_for_each_entry(list, &hdev->debug_list, node) { list_for_each_entry(list, &hdev->debug_list, node) {
for (i = 0; i < strlen(buf); i++) for (i = 0; i < strlen(buf); i++)
list->hid_debug_buf[(list->tail + i) % HID_DEBUG_BUFSIZE] = list->hid_debug_buf[(list->tail + i) % HID_DEBUG_BUFSIZE] =
buf[i]; buf[i];
list->tail = (list->tail + i) % HID_DEBUG_BUFSIZE; list->tail = (list->tail + i) % HID_DEBUG_BUFSIZE;
} }
mutex_unlock(&hdev->debug_list_lock); spin_unlock_irqrestore(&hdev->debug_list_lock, flags);
wake_up_interruptible(&hdev->debug_wait); wake_up_interruptible(&hdev->debug_wait);
} }
...@@ -977,6 +978,7 @@ static int hid_debug_events_open(struct inode *inode, struct file *file) ...@@ -977,6 +978,7 @@ static int hid_debug_events_open(struct inode *inode, struct file *file)
{ {
int err = 0; int err = 0;
struct hid_debug_list *list; struct hid_debug_list *list;
unsigned long flags;
if (!(list = kzalloc(sizeof(struct hid_debug_list), GFP_KERNEL))) { if (!(list = kzalloc(sizeof(struct hid_debug_list), GFP_KERNEL))) {
err = -ENOMEM; err = -ENOMEM;
...@@ -992,9 +994,9 @@ static int hid_debug_events_open(struct inode *inode, struct file *file) ...@@ -992,9 +994,9 @@ static int hid_debug_events_open(struct inode *inode, struct file *file)
file->private_data = list; file->private_data = list;
mutex_init(&list->read_mutex); mutex_init(&list->read_mutex);
mutex_lock(&list->hdev->debug_list_lock); spin_lock_irqsave(&list->hdev->debug_list_lock, flags);
list_add_tail(&list->node, &list->hdev->debug_list); list_add_tail(&list->node, &list->hdev->debug_list);
mutex_unlock(&list->hdev->debug_list_lock); spin_unlock_irqrestore(&list->hdev->debug_list_lock, flags);
out: out:
return err; return err;
...@@ -1088,10 +1090,11 @@ static unsigned int hid_debug_events_poll(struct file *file, poll_table *wait) ...@@ -1088,10 +1090,11 @@ static unsigned int hid_debug_events_poll(struct file *file, poll_table *wait)
static int hid_debug_events_release(struct inode *inode, struct file *file) static int hid_debug_events_release(struct inode *inode, struct file *file)
{ {
struct hid_debug_list *list = file->private_data; struct hid_debug_list *list = file->private_data;
unsigned long flags;
mutex_lock(&list->hdev->debug_list_lock); spin_lock_irqsave(&list->hdev->debug_list_lock, flags);
list_del(&list->node); list_del(&list->node);
mutex_unlock(&list->hdev->debug_list_lock); spin_unlock_irqrestore(&list->hdev->debug_list_lock, flags);
kfree(list->hid_debug_buf); kfree(list->hid_debug_buf);
kfree(list); kfree(list);
......
...@@ -515,7 +515,7 @@ struct hid_device { /* device report descriptor */ ...@@ -515,7 +515,7 @@ struct hid_device { /* device report descriptor */
struct dentry *debug_rdesc; struct dentry *debug_rdesc;
struct dentry *debug_events; struct dentry *debug_events;
struct list_head debug_list; struct list_head debug_list;
struct mutex debug_list_lock; spinlock_t debug_list_lock;
wait_queue_head_t debug_wait; wait_queue_head_t debug_wait;
}; };
......
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