diff mbox series

[RFC,4/6] Bluetooth: hci_sync: fix locking in hci_conn_abort and hci_disconnect_all_sync

Message ID 70a377ca228992facffcc57d77e9acdbde8e46a7.1690399379.git.pav@iki.fi (mailing list archive)
State New, archived
Headers show
Series Locking in hci_sync | expand

Checks

Context Check Description
tedd_an/pre-ci_am success Success
tedd_an/CheckPatch success CheckPatch PASS
tedd_an/GitLint fail WARNING: I3 - ignore-body-lines: gitlint will be switching from using Python regex 'match' (match beginning) to 'search' (match anywhere) semantics. Please review your ignore-body-lines.regex option accordingly. To remove this warning, set general.regex-style-search=True. More details: https://jorisroovers.github.io/gitlint/configuration/#regex-style-search 1: T1 Title exceeds max length (88>80): "[RFC,4/6] Bluetooth: hci_sync: fix locking in hci_conn_abort and hci_disconnect_all_sync"
tedd_an/SubjectPrefix success Gitlint PASS
tedd_an/IncrementalBuild success Incremental Build PASS

Commit Message

Pauli Virtanen July 26, 2023, 9:25 p.m. UTC
hci_conn_abort may run concurrently with operations that e.g. change
conn handle or delete it.  Similarly, hci_disconnect_all_sync iterates
conn_hash without locks/RCU which is not OK.

To make it correct vs locking, use hci_conn_sync_queue to hold
hdev->lock and check hci_conn aliveness after waiting for events.

Make iteration in hci_disconnect_all_sync safe. Since we don't have a
flag for indicating whether a connection was aborted, just make a copy
of the conn_hash and iterate the copy.

Signed-off-by: Pauli Virtanen <pav@iki.fi>
---

Notes:
    The iterate-over-copy is ugly, could add another hci_conn flag.  Or
    change things so that hci_conn_get keeps the hci_conn in conn_hash so we
    can always continue the iteration safely as long as a reference was
    held.

 net/bluetooth/hci_conn.c | 10 ++------
 net/bluetooth/hci_sync.c | 52 ++++++++++++++++++++++++++++------------
 2 files changed, 39 insertions(+), 23 deletions(-)
diff mbox series

Patch

diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c
index 208eb5eea451..ba339a0eb851 100644
--- a/net/bluetooth/hci_conn.c
+++ b/net/bluetooth/hci_conn.c
@@ -2845,12 +2845,7 @@  u32 hci_conn_get_phy(struct hci_conn *conn)
 
 static int abort_conn_sync(struct hci_dev *hdev, void *data)
 {
-	struct hci_conn *conn;
-	u16 handle = PTR_ERR(data);
-
-	conn = hci_conn_hash_lookup_handle(hdev, handle);
-	if (!conn)
-		return 0;
+	struct hci_conn *conn = data;
 
 	return hci_abort_conn_sync(hdev, conn, conn->abort_reason);
 }
@@ -2886,8 +2881,7 @@  int hci_abort_conn(struct hci_conn *conn, u8 reason)
 		}
 	}
 
-	return hci_cmd_sync_queue(hdev, abort_conn_sync, ERR_PTR(conn->handle),
-				  NULL);
+	return hci_conn_sync_queue(conn, abort_conn_sync, conn, NULL);
 }
 
 struct hci_conn_sync_work_entry {
diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c
index 6a295a9e1f5d..101548fe81da 100644
--- a/net/bluetooth/hci_sync.c
+++ b/net/bluetooth/hci_sync.c
@@ -5407,6 +5407,8 @@  int hci_abort_conn_sync(struct hci_dev *hdev, struct hci_conn *conn, u8 reason)
 {
 	int err;
 
+	lockdep_assert_held(&hdev->lock);
+
 	switch (conn->state) {
 	case BT_CONNECTED:
 	case BT_CONFIG:
@@ -5418,21 +5420,15 @@  int hci_abort_conn_sync(struct hci_dev *hdev, struct hci_conn *conn, u8 reason)
 		 * or in case of LE it was still scanning so it can be cleanup
 		 * safely.
 		 */
-		if (err) {
-			hci_dev_lock(hdev);
+		if (err && hci_conn_is_alive(hdev, conn))
 			hci_conn_failed(conn, err);
-			hci_dev_unlock(hdev);
-		}
 		return err;
 	case BT_CONNECT2:
 		return hci_reject_conn_sync(hdev, conn, reason);
 	case BT_OPEN:
 		/* Cleanup bises that failed to be established */
-		if (test_and_clear_bit(HCI_CONN_BIG_SYNC_FAILED, &conn->flags)) {
-			hci_dev_lock(hdev);
+		if (test_and_clear_bit(HCI_CONN_BIG_SYNC_FAILED, &conn->flags))
 			hci_conn_failed(conn, reason);
-			hci_dev_unlock(hdev);
-		}
 		break;
 	default:
 		conn->state = BT_CLOSED;
@@ -5444,16 +5440,42 @@  int hci_abort_conn_sync(struct hci_dev *hdev, struct hci_conn *conn, u8 reason)
 
 static int hci_disconnect_all_sync(struct hci_dev *hdev, u8 reason)
 {
-	struct hci_conn *conn, *tmp;
-	int err;
+	struct hci_conn *conn;
+	struct hci_conn **conns;
+	int err = 0;
+	size_t i, n;
 
-	list_for_each_entry_safe(conn, tmp, &hdev->conn_hash.list, list) {
-		err = hci_abort_conn_sync(hdev, conn, reason);
-		if (err)
-			return err;
+	hci_cmd_sync_dev_lock(hdev);
+
+	/* Make a copy of conn_hash, because hci_abort_conn_sync may release the
+	 * lock and wait for events, during which the list may be mutated
+	 * arbitrarily.
+	 */
+	n = 0;
+	list_for_each_entry(conn, &hdev->conn_hash.list, list)
+		++n;
+
+	conns = kvcalloc(n, sizeof(*conns), GFP_KERNEL);
+	if (!conns) {
+		err = -ENOMEM;
+		goto unlock;
 	}
 
-	return 0;
+	i = 0;
+	list_for_each_entry(conn, &hdev->conn_hash.list, list)
+		conns[i++] = hci_conn_get(conn);
+
+	for (i = 0; i < n; ++i) {
+		if (!err)
+			err = hci_abort_conn_sync(hdev, conns[i], reason);
+		hci_conn_put(conns[i]);
+	}
+
+	kvfree(conns);
+
+unlock:
+	hci_cmd_sync_dev_unlock(hdev);
+	return err;
 }
 
 /* This function perform power off HCI command sequence as follows: