summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJukka Taimisto <jtt@codenomicon.com>2014-06-12 10:15:13 +0000
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2014-07-09 11:21:27 -0700
commitee805f9499ebd0edf0877990968543c752043b59 (patch)
treea8deb8ca91f5b0790a7bc8e38d37d3dc31d23656
parent14ec593d6bb050cf40a4ade2f9ac9ca050e0412c (diff)
downloadlwn-ee805f9499ebd0edf0877990968543c752043b59.tar.gz
lwn-ee805f9499ebd0edf0877990968543c752043b59.zip
Bluetooth: Fix deadlock in l2cap_conn_del()
commit 7ab56c3a6eccb215034b0cb096e0313441cbf2a4 upstream. A deadlock occurs when PDU containing invalid SMP opcode is received on Security Manager Channel over LE link and conn->pending_rx_work worker has not run yet. When LE link is created l2cap_conn_ready() is called and before returning it schedules conn->pending_rx_work worker to hdev->workqueue. Incoming data to SMP fixed channel is handled by l2cap_recv_frame() which calls smp_sig_channel() to handle the SMP PDU. If smp_sig_channel() indicates failure l2cap_conn_del() is called to delete the connection. When deleting the connection, l2cap_conn_del() purges the pending_rx queue and calls flush_work() to wait for the pending_rx_work worker to complete. Since incoming data is handled by a worker running from the same workqueue as the pending_rx_work is being scheduled on, we will deadlock on waiting for pending_rx_work to complete. This patch fixes the deadlock by calling cancel_work_sync() instead of flush_work(). Signed-off-by: Jukka Taimisto <jtt@codenomicon.com> Signed-off-by: Marcel Holtmann <marcel@holtmann.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r--net/bluetooth/l2cap_core.c8
1 files changed, 7 insertions, 1 deletions
diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index dc4d301d3a72..1c97b7a4f170 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -1657,7 +1657,13 @@ static void l2cap_conn_del(struct hci_conn *hcon, int err)
kfree_skb(conn->rx_skb);
skb_queue_purge(&conn->pending_rx);
- flush_work(&conn->pending_rx_work);
+
+ /* We can not call flush_work(&conn->pending_rx_work) here since we
+ * might block if we are running on a worker from the same workqueue
+ * pending_rx_work is waiting on.
+ */
+ if (work_pending(&conn->pending_rx_work))
+ cancel_work_sync(&conn->pending_rx_work);
l2cap_unregister_all_users(conn);