Browse Source

NFC: Fix sleeping in invalid context when netlink socket is closed

netlink_register_notifier requires notify functions to not sleep.
nfc_stop_poll locks device mutex and must not be called from notifier.
Create workqueue that will handle this for all devices.

BUG: sleeping function called from invalid context at kernel/mutex.c:269
in_atomic(): 0, irqs_disabled(): 0, pid: 4497, name: neard
1 lock held by neard/4497:
Pid: 4497, comm: neard Not tainted 3.5.0-999-nfc+ #5
Call Trace:
[<ffffffff810952c5>] __might_sleep+0x145/0x200
[<ffffffff81743dde>] mutex_lock_nested+0x2e/0x50
[<ffffffff816ffd19>] nfc_stop_poll+0x39/0xb0
[<ffffffff81700a17>] nfc_genl_rcv_nl_event+0x77/0xc0
[<ffffffff8174aa8c>] notifier_call_chain+0x5c/0x120
[<ffffffff8174abd6>] __atomic_notifier_call_chain+0x86/0x140
[<ffffffff8174ab50>] ? notifier_call_chain+0x120/0x120
[<ffffffff815e1347>] ? skb_dequeue+0x67/0x90
[<ffffffff8174aca6>] atomic_notifier_call_chain+0x16/0x20
[<ffffffff8162119a>] netlink_release+0x24a/0x280
[<ffffffff815d7aa8>] sock_release+0x28/0xa0
[<ffffffff815d7be7>] sock_close+0x17/0x30
[<ffffffff811b2a7c>] __fput+0xcc/0x250
[<ffffffff811b2c0e>] ____fput+0xe/0x10
[<ffffffff81085009>] task_work_run+0x69/0x90
[<ffffffff8101b951>] do_notify_resume+0x81/0xd0
[<ffffffff8174ef22>] int_signal+0x12/0x17

Signed-off-by: Szymon Janc <szymon.janc@tieto.com>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
Szymon Janc 12 years ago
parent
commit
3c0cc8aa23
1 changed files with 39 additions and 7 deletions
  1. 39 7
      net/nfc/netlink.c

+ 39 - 7
net/nfc/netlink.c

@@ -761,31 +761,63 @@ static struct genl_ops nfc_genl_ops[] = {
 	},
 };
 
-static int nfc_genl_rcv_nl_event(struct notifier_block *this,
-				 unsigned long event, void *ptr)
+
+struct urelease_work {
+	struct	work_struct w;
+	int	pid;
+};
+
+static void nfc_urelease_event_work(struct work_struct *work)
 {
-	struct netlink_notify *n = ptr;
+	struct urelease_work *w = container_of(work, struct urelease_work, w);
 	struct class_dev_iter iter;
 	struct nfc_dev *dev;
 
-	if (event != NETLINK_URELEASE || n->protocol != NETLINK_GENERIC)
-		goto out;
+	pr_debug("pid %d\n", w->pid);
 
-	pr_debug("NETLINK_URELEASE event from id %d\n", n->pid);
+	mutex_lock(&nfc_devlist_mutex);
 
 	nfc_device_iter_init(&iter);
 	dev = nfc_device_iter_next(&iter);
 
 	while (dev) {
-		if (dev->genl_data.poll_req_pid == n->pid) {
+		mutex_lock(&dev->genl_data.genl_data_mutex);
+
+		if (dev->genl_data.poll_req_pid == w->pid) {
 			nfc_stop_poll(dev);
 			dev->genl_data.poll_req_pid = 0;
 		}
+
+		mutex_unlock(&dev->genl_data.genl_data_mutex);
+
 		dev = nfc_device_iter_next(&iter);
 	}
 
 	nfc_device_iter_exit(&iter);
 
+	mutex_unlock(&nfc_devlist_mutex);
+
+	kfree(w);
+}
+
+static int nfc_genl_rcv_nl_event(struct notifier_block *this,
+				 unsigned long event, void *ptr)
+{
+	struct netlink_notify *n = ptr;
+	struct urelease_work *w;
+
+	if (event != NETLINK_URELEASE || n->protocol != NETLINK_GENERIC)
+		goto out;
+
+	pr_debug("NETLINK_URELEASE event from id %d\n", n->pid);
+
+	w = kmalloc(sizeof(*w), GFP_ATOMIC);
+	if (w) {
+		INIT_WORK((struct work_struct *) w, nfc_urelease_event_work);
+		w->pid = n->pid;
+		schedule_work((struct work_struct *) w);
+	}
+
 out:
 	return NOTIFY_DONE;
 }