mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Kyle Pepper <cochcoder@proton.me>
To: linux-bluetooth@vger.kernel.org
Cc: luiz.dentz@gmail.com, marcel@holtmann.org, linux-kernel@vger.kernel.org
Subject: [PATCH v11 2/2] Bluetooth: L2CAP: Keep credits from completing security upgrades
Date: Fri, 13 Nov 2026 14:00:56 +0000	[thread overview]
Message-ID: <cfa7a4ee41a18ad131aa77c80e82c86f7854a190.1794550328.git.github.filter194@slmail.me> (raw)
In-Reply-To: <20261113030556.75484-1-cochcoder@proton.me>

A credit indication can drain queued CoC data and clear socket suspension
while BT_SECURITY is waiting for stronger security. Keep the request
pending until the required encryption, level, key size and MIC are met.

Finish requests on the existing HCI workqueue, outside the confirmation
list walk. Hold selected channels before callbacks can remove siblings.
Latch SMP failure before admitting a fresh request, clear pending flags on
close, and recheck channel state before processing deferred confirmations.
Use the existing pairing and HCI timeouts without a new request deadline.

Reject overlapping requests with EBUSY while pairing, key distribution or
encryption is in progress. This includes repeated and lowered requests;
applications can retry after the current procedure completes. Return
EACCES when SMP starts no procedure and the requirements remain unmet,
restoring the channel requirements and both pending link requirements.
Preserve each CoC's requested level across shared-link confirmations.

Keep deferred reads pending during an upgrade. Send an ECRED response
before releasing accepted children's data, let rejection override pending
siblings, and close every deleted child. Return the batch's security error
to a deferred reader while preserving ordinary accept/close behavior.
Purge retained CoC data even if deferred acceptance left configuration
incomplete. Acquire the SMP channel lock without holding the socket lock,
and suspend pending ATT sockets until their confirmation resumes them.

Fixes: fad5fc89594d ("Bluetooth: Add LE flow control discipline")
Assisted-by: LLM
Signed-off-by: Kyle Pepper <cochcoder@proton.me>
---
 include/net/bluetooth/l2cap.h |   9 +-
 net/bluetooth/l2cap_core.c    | 326 +++++++++++++++++++++++++++++++---
 net/bluetooth/l2cap_sock.c    | 181 ++++++++++++-------
 net/bluetooth/smp.c           |   1 +
 4 files changed, 428 insertions(+), 89 deletions(-)

diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
index cf5c2df68..b004cc643 100644
--- a/include/net/bluetooth/l2cap.h
+++ b/include/net/bluetooth/l2cap.h
@@ -604,6 +604,7 @@ struct l2cap_chan {
 	__u32		remote_flush_to;
 
 	struct delayed_work	chan_timer;
+	struct list_head	security_list;
 	struct delayed_work	retrans_timer;
 	struct delayed_work	monitor_timer;
 	struct delayed_work	ack_timer;
@@ -644,6 +645,8 @@ struct l2cap_conn {
 
 	struct sk_buff_head	pending_rx;
 	struct work_struct	pending_rx_work;
+	struct work_struct	security_work;
+	atomic_t		security_failure;
 	bool			pending_rx_blocked __guarded_by(&lock);
 
 	struct delayed_work	id_addr_timer;
@@ -767,6 +770,8 @@ enum {
 	FLAG_ECRED_CONN_REQ_SENT,
 	FLAG_ECRED_RECONF_REQ_SENT,
 	FLAG_PENDING_SECURITY,
+	FLAG_SECURITY_CFM,
+	FLAG_SECURITY_FAILED,
 	FLAG_HOLD_HCI_CONN,
 	FLAG_DEL,
 };
@@ -974,7 +979,7 @@ void l2cap_cleanup_sockets(void);
 bool l2cap_is_socket(struct socket *sock);
 
 void __l2cap_le_connect_rsp_defer(struct l2cap_chan *chan);
-void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan)
+int __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan)
 	__must_hold(&chan->lock) __must_hold(&chan->conn->lock);
 void __l2cap_connect_rsp_defer(struct l2cap_chan *chan);
 
@@ -993,6 +998,8 @@ void l2cap_chan_busy(struct l2cap_chan *chan, int busy);
 void l2cap_chan_rx_avail(struct l2cap_chan *chan, ssize_t rx_avail);
 void l2cap_chan_req_mic_len(struct l2cap_chan *chan, struct hci_conn *hcon);
 int l2cap_chan_check_security(struct l2cap_chan *chan, bool initiator);
+int l2cap_chan_set_security(struct l2cap_chan *chan, u8 level, u8 mic_len);
+void l2cap_security_failure(struct l2cap_conn *conn);
 void l2cap_chan_set_defaults(struct l2cap_chan *chan, struct l2cap_chan *pchan);
 int l2cap_ertm_init(struct l2cap_chan *chan);
 void l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan);
diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index 4e8aec98e..87e5d107a 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -59,6 +59,7 @@ static void l2cap_tx(struct l2cap_chan *chan, struct l2cap_ctrl *control,
 static void l2cap_retrans_timeout(struct work_struct *work);
 static void l2cap_monitor_timeout(struct work_struct *work);
 static void l2cap_ack_timeout(struct work_struct *work);
+static void l2cap_security_work(struct work_struct *work);
 static void __l2cap_chan_close(struct l2cap_chan *chan, int reason)
 	__must_hold(&chan->lock) __must_hold(&chan->conn->lock);
 
@@ -479,6 +480,7 @@ struct l2cap_chan *l2cap_chan_create(void)
 	write_unlock(&chan_list_lock);
 
 	INIT_DELAYED_WORK(&chan->chan_timer, l2cap_chan_timeout);
+	INIT_LIST_HEAD(&chan->security_list);
 	INIT_DELAYED_WORK(&chan->retrans_timer, l2cap_retrans_timeout);
 	INIT_DELAYED_WORK(&chan->monitor_timer, l2cap_monitor_timeout);
 	INIT_DELAYED_WORK(&chan->ack_timer, l2cap_ack_timeout);
@@ -709,7 +711,10 @@ void l2cap_chan_del(struct l2cap_chan *chan, int err)
 	BT_DBG("chan %p, err %d, state %s", chan, err,
 	       state_to_string(chan->state));
 
+	clear_bit(FLAG_PENDING_SECURITY, &chan->flags);
+	clear_bit(FLAG_SECURITY_CFM, &chan->flags);
 	chan->ops->teardown(chan, err);
+	clear_bit(FLAG_SECURITY_FAILED, &chan->flags);
 
 	if (!test_and_set_bit(FLAG_DEL, &chan->flags)) {
 		/* Delete from channel list */
@@ -726,6 +731,12 @@ void l2cap_chan_del(struct l2cap_chan *chan, int err)
 			hci_conn_drop(chan->conn->hcon);
 	}
 
+	if (chan->mode == L2CAP_MODE_LE_FLOWCTL ||
+	    chan->mode == L2CAP_MODE_EXT_FLOWCTL) {
+		skb_queue_purge(&chan->tx_q);
+		return;
+	}
+
 	if (test_bit(CONF_NOT_COMPLETE, &chan->conf_state))
 		return;
 
@@ -733,11 +744,6 @@ void l2cap_chan_del(struct l2cap_chan *chan, int err)
 	case L2CAP_MODE_BASIC:
 		break;
 
-	case L2CAP_MODE_LE_FLOWCTL:
-	case L2CAP_MODE_EXT_FLOWCTL:
-		skb_queue_purge(&chan->tx_q);
-		break;
-
 	case L2CAP_MODE_ERTM:
 		__clear_retrans_timer(chan);
 		__clear_monitor_timer(chan);
@@ -870,6 +876,9 @@ static void __l2cap_chan_close(struct l2cap_chan *chan, int reason)
 
 	BT_DBG("chan %p state %s", chan, state_to_string(chan->state));
 
+	clear_bit(FLAG_PENDING_SECURITY, &chan->flags);
+	clear_bit(FLAG_SECURITY_CFM, &chan->flags);
+
 	switch (chan->state) {
 	case BT_LISTEN:
 		chan->ops->teardown(chan, 0);
@@ -2705,12 +2714,21 @@ static int l2cap_segment_le_sdu(struct l2cap_chan *chan,
 	return 0;
 }
 
-static void l2cap_le_flowctl_send(struct l2cap_chan *chan)
+static bool l2cap_le_flowctl_send(struct l2cap_chan *chan)
 {
 	int sent = 0;
 
 	BT_DBG("chan %p", chan);
 
+	if (chan->state != BT_CONNECTED ||
+	    test_bit(FLAG_DEL, &chan->flags) ||
+	    test_bit(FLAG_PENDING_SECURITY, &chan->flags) ||
+	    test_bit(FLAG_SECURITY_FAILED, &chan->flags) ||
+	    (chan->mode == L2CAP_MODE_EXT_FLOWCTL && chan->ident &&
+	     test_bit(FLAG_DEFER_SETUP, &chan->flags) &&
+	     !test_bit(FLAG_ECRED_CONN_REQ_SENT, &chan->flags)))
+		return false;
+
 	while (chan->tx_credits && !skb_queue_empty(&chan->tx_q)) {
 		l2cap_do_send(chan, skb_dequeue(&chan->tx_q));
 		chan->tx_credits--;
@@ -2719,6 +2737,198 @@ static void l2cap_le_flowctl_send(struct l2cap_chan *chan)
 
 	BT_DBG("Sent %d credits %u queued %u", sent, chan->tx_credits,
 	       skb_queue_len(&chan->tx_q));
+
+	return true;
+}
+
+static void l2cap_le_flowctl_resume(struct l2cap_chan *chan)
+	__must_hold(&chan->lock)
+{
+	if (l2cap_le_flowctl_send(chan) && chan->tx_credits)
+		chan->ops->resume(chan);
+	else
+		chan->ops->suspend(chan);
+}
+
+static bool l2cap_chan_security_sufficient(struct l2cap_chan *chan)
+{
+	struct hci_conn *hcon = chan->conn->hcon;
+
+	lockdep_assert_held(&hcon->hdev->lock);
+	lockdep_assert_held(&chan->lock);
+
+	return smp_sufficient_security(hcon, chan->sec_level, SMP_ALLOW_STK) &&
+	       (chan->sec_level == BT_SECURITY_LOW ||
+		test_bit(HCI_CONN_ENCRYPT, hcon->flags)) &&
+	       l2cap_check_enc_key_size(hcon, chan) &&
+	       l2cap_check_mic_len(hcon, chan);
+}
+
+static bool l2cap_chan_security_ready(struct l2cap_chan *chan)
+{
+	return !test_bit(HCI_CONN_ENCRYPT_PEND, chan->conn->hcon->flags) &&
+	       l2cap_chan_security_sufficient(chan);
+}
+
+static void l2cap_chan_security_complete(struct l2cap_chan *chan)
+	__must_hold(&chan->lock) __must_hold(&chan->conn->lock)
+{
+	clear_bit(FLAG_PENDING_SECURITY, &chan->flags);
+	clear_bit(FLAG_SECURITY_CFM, &chan->flags);
+
+	if (chan->state == BT_CONNECT2) {
+		chan->ops->resume(chan);
+		return;
+	}
+
+	if (chan->mode == L2CAP_MODE_EXT_FLOWCTL)
+		__l2cap_ecred_conn_rsp_defer(chan);
+	if (!test_bit(FLAG_DEL, &chan->flags))
+		l2cap_le_flowctl_resume(chan);
+}
+
+static void l2cap_queue_security_work(struct l2cap_conn *conn)
+{
+	struct hci_dev *hdev = conn->hcon->hdev;
+
+	/* Pair with the RCU grace period before hci_dev_close_sync() drains
+	 * this queue. No security work may be added after that drain.
+	 */
+	rcu_read_lock();
+	if (test_bit(HCI_UP, &hdev->flags)) {
+		l2cap_conn_get(conn);
+		if (!queue_work(hdev->workqueue, &conn->security_work))
+			l2cap_conn_put(conn);
+	}
+	rcu_read_unlock();
+}
+
+void l2cap_security_failure(struct l2cap_conn *conn)
+{
+	/* SMP receive holds conn->lock; management and encryption hold hdev. */
+	lockdep_assert(lockdep_is_held(&conn->lock) ||
+		       lockdep_is_held(&conn->hcon->hdev->lock));
+	if (conn->hcon->type != LE_LINK)
+		return;
+
+	atomic_set(&conn->security_failure, 1);
+	l2cap_queue_security_work(conn);
+}
+
+static void l2cap_security_work(struct work_struct *work)
+{
+	struct l2cap_conn *conn = container_of(work, struct l2cap_conn,
+					       security_work);
+	struct l2cap_chan *chan, *next;
+	LIST_HEAD(pending);
+
+	hci_dev_lock(conn->hcon->hdev);
+	mutex_lock(&conn->lock);
+	if (!test_bit(HCI_UP, &conn->hcon->hdev->flags) ||
+	    conn->hcon->l2cap_data != conn)
+		goto done;
+
+	if (atomic_xchg(&conn->security_failure, 0)) {
+		list_for_each_entry(chan, &conn->chan_l, list) {
+			l2cap_chan_lock(chan);
+			if ((chan->state == BT_CONNECTED ||
+			     chan->state == BT_CONNECT2) &&
+			    (chan->mode == L2CAP_MODE_LE_FLOWCTL ||
+			     chan->mode == L2CAP_MODE_EXT_FLOWCTL) &&
+			    test_bit(FLAG_PENDING_SECURITY, &chan->flags)) {
+				set_bit(FLAG_SECURITY_FAILED, &chan->flags);
+				set_bit(FLAG_SECURITY_CFM, &chan->flags);
+			}
+			l2cap_chan_unlock(chan);
+		}
+	}
+
+	/* Hold all selected channels before a callback can remove siblings. */
+	list_for_each_entry(chan, &conn->chan_l, list) {
+		l2cap_chan_lock(chan);
+		if (test_and_clear_bit(FLAG_SECURITY_CFM, &chan->flags) &&
+		    (chan->state == BT_CONNECTED || chan->state == BT_CONNECT2)) {
+			l2cap_chan_hold(chan);
+			list_add_tail(&chan->security_list, &pending);
+		}
+		l2cap_chan_unlock(chan);
+	}
+
+	list_for_each_entry_safe(chan, next, &pending, security_list) {
+		list_del_init(&chan->security_list);
+		l2cap_chan_lock(chan);
+		lockdep_assert_held(&chan->conn->lock);
+		if ((chan->state == BT_CONNECTED || chan->state == BT_CONNECT2) &&
+		    test_bit(FLAG_PENDING_SECURITY, &chan->flags)) {
+			if (!test_bit(FLAG_SECURITY_FAILED, &chan->flags) &&
+			    l2cap_chan_security_ready(chan)) {
+				l2cap_chan_security_complete(chan);
+			} else {
+				set_bit(FLAG_SECURITY_FAILED, &chan->flags);
+				if (chan->mode == L2CAP_MODE_EXT_FLOWCTL &&
+				    chan->ident &&
+				    test_bit(FLAG_DEFER_SETUP, &chan->flags) &&
+				    !test_bit(FLAG_ECRED_CONN_REQ_SENT, &chan->flags))
+					l2cap_chan_ecred_connect_reject(chan);
+				else
+					__l2cap_chan_close(chan, ECONNREFUSED);
+				chan->ops->close(chan);
+			}
+		}
+		l2cap_chan_unlock(chan);
+		l2cap_chan_put(chan);
+	}
+done:
+	mutex_unlock(&conn->lock);
+	hci_dev_unlock(conn->hcon->hdev);
+	l2cap_conn_put(conn);
+}
+
+int l2cap_chan_set_security(struct l2cap_chan *chan, u8 level, u8 mic_len)
+{
+	struct hci_conn *hcon = chan->conn->hcon;
+	u8 old_level = chan->sec_level, old_mic = chan->mic_len;
+	u8 old_req_mic = hcon->req_mic_len;
+	u8 old_pending = hcon->pending_sec_level;
+	int err = 0;
+
+	lockdep_assert_held(&hcon->hdev->lock);
+	lockdep_assert_held(&chan->conn->lock);
+	lockdep_assert_held(&chan->lock);
+
+	if (test_bit(FLAG_SECURITY_FAILED, &chan->flags))
+		return -ECONNREFUSED;
+	/* Do not admit a new request before an old SMP failure is dispatched. */
+	if (atomic_read(&chan->conn->security_failure))
+		return -EBUSY;
+	/* Do not change the requirements of a procedure already in progress. */
+	if (test_bit(FLAG_PENDING_SECURITY, &chan->flags) ||
+	    test_bit(HCI_CONN_ENCRYPT_PEND, hcon->flags) ||
+	    smp_pairing_in_progress(hcon))
+		return -EBUSY;
+
+	chan->sec_level = level;
+	chan->mic_len = mic_len;
+	if (!l2cap_chan_check_security(chan, true) ||
+	    test_bit(HCI_CONN_ENCRYPT_PEND, hcon->flags)) {
+		set_bit(FLAG_PENDING_SECURITY, &chan->flags);
+		clear_bit(FLAG_SECURITY_CFM, &chan->flags);
+		chan->ops->suspend(chan);
+		return 0;
+	} else if (!l2cap_chan_security_ready(chan)) {
+		/* SMP did not start a procedure that could meet this request. */
+		err = -EACCES;
+		goto restore;
+	}
+
+	l2cap_chan_security_complete(chan);
+	return 0;
+restore:
+	chan->sec_level = old_level;
+	chan->mic_len = old_mic;
+	hcon->req_mic_len = old_req_mic;
+	hcon->pending_sec_level = old_pending;
+	return err;
 }
 
 static void l2cap_tx_timestamp(struct sk_buff *skb,
@@ -2791,9 +3001,7 @@ int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len,
 
 		skb_queue_splice_tail_init(&seg_queue, &chan->tx_q);
 
-		l2cap_le_flowctl_send(chan);
-
-		if (!chan->tx_credits)
+		if (!l2cap_le_flowctl_send(chan) || !chan->tx_credits)
 			chan->ops->suspend(chan);
 
 		err = len;
@@ -4043,7 +4251,14 @@ static void l2cap_ecred_list_defer(struct l2cap_chan *chan, void *data)
 {
 	int *result = data;
 
-	if (*result || test_bit(FLAG_ECRED_CONN_REQ_SENT, &chan->flags))
+	if (chan->mode != L2CAP_MODE_EXT_FLOWCTL ||
+	    test_bit(FLAG_ECRED_CONN_REQ_SENT, &chan->flags))
+		return;
+	if (test_bit(FLAG_SECURITY_FAILED, &chan->flags)) {
+		*result = -ECONNREFUSED;
+		return;
+	}
+	if (*result < 0)
 		return;
 
 	switch (chan->state) {
@@ -4052,6 +4267,8 @@ static void l2cap_ecred_list_defer(struct l2cap_chan *chan, void *data)
 		(*result)++;
 		return;
 	case BT_CONNECTED:
+		if (test_bit(FLAG_PENDING_SECURITY, &chan->flags))
+			(*result)++;
 		return;
 	default:
 		/* If not connected or pending accept it has been refused */
@@ -4062,6 +4279,8 @@ static void l2cap_ecred_list_defer(struct l2cap_chan *chan, void *data)
 
 struct l2cap_ecred_rsp_data {
 	struct l2cap_chan *locked_chan;
+	struct l2cap_chan *accepted[L2CAP_ECRED_MAX_CID];
+	bool security_failed;
 	struct {
 		struct l2cap_ecred_conn_rsp_hdr rsp;
 		__le16 scid[L2CAP_ECRED_MAX_CID];
@@ -4140,10 +4359,17 @@ static void l2cap_ecred_rsp_defer(struct l2cap_chan *chan, void *data)
 		rsp->pdu.rsp.result = cpu_to_le16(L2CAP_CR_LE_NO_MEM);
 
 	/* Include all channels pending with the same ident */
-	if (!rsp->pdu.rsp.result)
+	if (!rsp->pdu.rsp.result) {
+		rsp->accepted[rsp->count] = chan;
+		l2cap_chan_hold(chan);
 		rsp_flex->dcid[rsp->count++] = cpu_to_le16(chan->scid);
-	else
-		l2cap_chan_del(chan, ECONNRESET);
+	} else {
+		if (rsp->security_failed)
+			set_bit(FLAG_SECURITY_FAILED, &chan->flags);
+		l2cap_chan_del(chan, rsp->security_failed ?
+			      ECONNREFUSED : ECONNRESET);
+		chan->ops->close(chan);
+	}
 
 done:
 	if (locked)
@@ -4152,23 +4378,32 @@ static void l2cap_ecred_rsp_defer(struct l2cap_chan *chan, void *data)
 	l2cap_chan_put(chan);
 }
 
-void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan)
+int __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan)
 {
 	struct l2cap_conn *conn = chan->conn;
 	struct l2cap_ecred_rsp_data data;
+	struct l2cap_chan *sibling;
 	u16 id = chan->ident;
 	int result = 0;
+	int i;
 
 	if (!id)
-		return;
+		return 0;
 	if (!test_bit(FLAG_DEFER_SETUP, &chan->flags))
-		return;
+		return 0;
 
 	BT_DBG("chan %p id %d", chan, id);
 
 	memset(&data, 0, sizeof(data));
 
 	data.locked_chan = chan;
+	list_for_each_entry(sibling, &conn->chan_l, list) {
+		if (sibling->ident == id &&
+		    sibling->mode == L2CAP_MODE_EXT_FLOWCTL &&
+		    !test_bit(FLAG_ECRED_CONN_REQ_SENT, &sibling->flags) &&
+		    test_bit(FLAG_SECURITY_FAILED, &sibling->flags))
+			data.security_failed = true;
+	}
 
 	data.pdu.rsp.mtu     = cpu_to_le16(chan->imtu);
 	data.pdu.rsp.mps     = cpu_to_le16(chan->mps);
@@ -4179,7 +4414,7 @@ void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan)
 	__l2cap_chan_list_id(conn, id, l2cap_ecred_list_defer, &result);
 
 	if (result > 0)
-		return;
+		return 0;
 
 	if (result < 0)
 		data.pdu.rsp.result = cpu_to_le16(L2CAP_CR_LE_AUTHORIZATION);
@@ -4190,6 +4425,24 @@ void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan)
 	l2cap_send_cmd(conn, id, L2CAP_ECRED_CONN_RSP,
 		       sizeof(data.pdu.rsp) + (data.count * sizeof(__le16)),
 		       &data.pdu);
+
+	/* Data must follow the connection response on the wire. */
+	for (i = 0; i < data.count; i++) {
+		struct l2cap_chan *accepted = data.accepted[i];
+		bool locked = l2cap_chan_try_sibling_lock(accepted, chan);
+
+		if (locked) {
+			if (!test_bit(FLAG_DEL, &accepted->flags))
+				l2cap_le_flowctl_resume(accepted);
+			l2cap_chan_unlock(accepted);
+		} else if (accepted == chan &&
+			   !test_bit(FLAG_DEL, &chan->flags)) {
+			l2cap_le_flowctl_resume(chan);
+		}
+		l2cap_chan_put(accepted);
+	}
+
+	return data.security_failed ? -ECONNREFUSED : 0;
 }
 
 void __l2cap_connect_rsp_defer(struct l2cap_chan *chan)
@@ -5483,10 +5736,7 @@ static inline int l2cap_le_credits(struct l2cap_conn *conn,
 	chan->tx_credits += credits;
 
 	/* Resume sending */
-	l2cap_le_flowctl_send(chan);
-
-	if (chan->tx_credits)
-		chan->ops->resume(chan);
+	l2cap_le_flowctl_resume(chan);
 
 unlock:
 	l2cap_chan_unlock(chan);
@@ -7623,6 +7873,8 @@ static struct l2cap_conn *l2cap_conn_add(struct hci_conn *hcon)
 
 	skb_queue_head_init(&conn->pending_rx);
 	INIT_WORK(&conn->pending_rx_work, process_pending_rx);
+	INIT_WORK(&conn->security_work, l2cap_security_work);
+	atomic_set(&conn->security_failure, 0);
 	INIT_DELAYED_WORK(&conn->id_addr_timer, l2cap_conn_update_id_addr);
 
 	conn->disc_reason = HCI_ERROR_REMOTE_USER_TERM;
@@ -8089,6 +8341,8 @@ static void l2cap_security_cfm(struct hci_conn *hcon, u8 status, u8 encrypt)
 	mutex_lock(&conn->lock);
 
 	list_for_each_entry(chan, &conn->chan_l, list) {
+		bool le_flowctl;
+
 		l2cap_chan_lock(chan);
 
 		lockdep_assert_held(&chan->conn->lock);
@@ -8096,7 +8350,10 @@ static void l2cap_security_cfm(struct hci_conn *hcon, u8 status, u8 encrypt)
 		BT_DBG("chan %p scid 0x%4.4x state %s", chan, chan->scid,
 		       state_to_string(chan->state));
 
-		if (!status && encrypt)
+		le_flowctl = hcon->type == LE_LINK &&
+			     (chan->mode == L2CAP_MODE_LE_FLOWCTL ||
+			      chan->mode == L2CAP_MODE_EXT_FLOWCTL);
+		if (!status && encrypt && !le_flowctl)
 			chan->sec_level = hcon->sec_level;
 
 		if (!__l2cap_no_conn_pending(chan)) {
@@ -8104,6 +8361,31 @@ static void l2cap_security_cfm(struct hci_conn *hcon, u8 status, u8 encrypt)
 			continue;
 		}
 
+		if (le_flowctl && (chan->state == BT_CONNECTED ||
+				   chan->state == BT_CONNECT2)) {
+			bool pending = test_bit(FLAG_PENDING_SECURITY, &chan->flags);
+
+			/* An authentication event may precede encryption completion. */
+			if (pending && !status &&
+			    test_bit(HCI_CONN_ENCRYPT_PEND, hcon->flags)) {
+				l2cap_chan_unlock(chan);
+				continue;
+			}
+			if ((status && pending) ||
+			    !l2cap_chan_security_sufficient(chan)) {
+				set_bit(FLAG_PENDING_SECURITY, &chan->flags);
+				set_bit(FLAG_SECURITY_FAILED, &chan->flags);
+			}
+			if (test_bit(FLAG_PENDING_SECURITY, &chan->flags)) {
+				set_bit(FLAG_SECURITY_CFM, &chan->flags);
+				l2cap_queue_security_work(conn);
+			} else if (!status && chan->state == BT_CONNECTED) {
+				l2cap_le_flowctl_resume(chan);
+			}
+			l2cap_chan_unlock(chan);
+			continue;
+		}
+
 		if (!status && (chan->state == BT_CONNECTED ||
 				chan->state == BT_CONFIG)) {
 			chan->ops->resume(chan);
diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
index d8a7b5f4b..c12bd17a4 100644
--- a/net/bluetooth/l2cap_sock.c
+++ b/net/bluetooth/l2cap_sock.c
@@ -929,12 +929,65 @@ static int l2cap_set_mode(struct l2cap_chan *chan, u8 mode)
 	return 0;
 }
 
+static int l2cap_sock_set_security(struct sock *sk, struct l2cap_chan *chan,
+				   const struct bt_security *sec)
+{
+	struct l2cap_conn *conn = l2cap_chan_conn(chan);
+	int err = 0;
+
+	lock_sock(sk);
+	if (conn && conn->hcon->type == LE_LINK &&
+	    (chan->mode == L2CAP_MODE_LE_FLOWCTL ||
+	     chan->mode == L2CAP_MODE_EXT_FLOWCTL)) {
+		if (sk->sk_state != BT_CONNECTED &&
+		    !(sk->sk_state == BT_CONNECT2 &&
+		      test_bit(BT_SK_DEFER_SETUP, &bt_sk(sk)->flags))) {
+			release_sock(sk);
+			return -EINVAL;
+		}
+		release_sock(sk);
+		return l2cap_chan_set_security(chan, sec->level, sec->mic_len);
+	}
+
+	chan->sec_level = sec->level;
+	chan->mic_len = sec->mic_len;
+	if (!conn)
+		goto done;
+	if (chan->scid == L2CAP_CID_ATT) {
+		release_sock(sk);
+		l2cap_chan_req_mic_len(chan, conn->hcon);
+		if (smp_conn_security(conn->hcon, sec->level))
+			return -EINVAL;
+		lock_sock(sk);
+		set_bit(FLAG_PENDING_SECURITY, &chan->flags);
+		set_bit(BT_SK_SUSPEND, &bt_sk(sk)->flags);
+		sk->sk_state = BT_CONFIG;
+		chan->state = BT_CONFIG;
+	} else if ((sk->sk_state == BT_CONNECT2 &&
+		    test_bit(BT_SK_DEFER_SETUP, &bt_sk(sk)->flags)) ||
+		   sk->sk_state == BT_CONNECTED) {
+		bool ready;
+
+		release_sock(sk);
+		ready = l2cap_chan_check_security(chan, true);
+		lock_sock(sk);
+		if (!ready)
+			set_bit(BT_SK_SUSPEND, &bt_sk(sk)->flags);
+		else
+			sk->sk_state_change(sk);
+	} else {
+		err = -EINVAL;
+	}
+done:
+	release_sock(sk);
+	return err;
+}
+
 static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname,
 				 sockptr_t optval, unsigned int optlen)
 {
 	struct sock *sk = sock->sk;
 	struct l2cap_chan *chan = l2cap_pi(sk)->chan;
-	struct bt_security sec;
 	struct bt_power pwr;
 	struct l2cap_conn *conn;
 	int err = 0;
@@ -950,78 +1003,59 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname,
 	if (level != SOL_BLUETOOTH)
 		return -ENOPROTOOPT;
 
-	l2cap_chan_lock(chan);
-	lock_sock(sk);
+	if (optname == BT_SECURITY) {
+		struct bt_security sec = { .level = BT_SECURITY_LOW };
+		bool have_conn;
 
-	switch (optname) {
-	case BT_SECURITY:
-		if (chan->chan_type != L2CAP_CHAN_CONN_ORIENTED &&
-		    chan->chan_type != L2CAP_CHAN_FIXED &&
-		    chan->chan_type != L2CAP_CHAN_RAW) {
-			err = -EINVAL;
-			break;
-		}
-
-		/* mic_len was added at the end of struct bt_security, so
-		 * accept the shorter struct as well, leaving mic_len unset.
-		 */
-		if (optlen < offsetofend(struct bt_security, key_size)) {
-			err = -EINVAL;
-			break;
-		}
+		/* Keep accepting the struct size used before mic_len was added. */
+		if (optlen < offsetofend(struct bt_security, key_size))
+			return -EINVAL;
 
-		err = copy_struct_from_sockptr(&sec, sizeof(sec), optval,
-					       optlen);
+		err = copy_struct_from_sockptr(&sec, sizeof(sec), optval, optlen);
 		if (err)
-			break;
-
-		if (sec.level < BT_SECURITY_LOW ||
-		    sec.level > BT_SECURITY_FIPS) {
-			err = -EINVAL;
-			break;
-		}
+			return err;
 
-		/* BLUETOOTH CORE SPECIFICATION Version 7.0 | Vol 4,
-		 * Part E, Section 7.7.8: MIC_Length 0x00 is 32 bits,
-		 * 0x01 is 64 bits and 0x02 is 128 bits; all other
-		 * values are reserved for future use.
-		 */
-		if (sec.mic_len > 0x02) {
-			err = -EINVAL;
-			break;
-		}
+		if (sec.level < BT_SECURITY_LOW || sec.level > BT_SECURITY_FIPS)
+			return -EINVAL;
 
-		chan->sec_level = sec.level;
-		chan->mic_len = sec.mic_len;
+		/* MIC length encodings 0, 1 and 2 mean 32, 64 and 128 bits. */
+		if (sec.mic_len > 0x02)
+			return -EINVAL;
 
+		l2cap_chan_lock(chan);
+		if (chan->chan_type != L2CAP_CHAN_CONN_ORIENTED &&
+		    chan->chan_type != L2CAP_CHAN_FIXED &&
+		    chan->chan_type != L2CAP_CHAN_RAW) {
+			l2cap_chan_unlock(chan);
+			return -EINVAL;
+		}
 		conn = l2cap_chan_conn(chan);
-		if (!conn)
-			break;
+		if (!conn) {
+			err = l2cap_sock_set_security(sk, chan, &sec);
+			l2cap_chan_unlock(chan);
+			return err;
+		}
+		l2cap_conn_get(conn);
+		l2cap_chan_unlock(chan);
 
-		/* change security for LE channels */
-		if (chan->scid == L2CAP_CID_ATT) {
-			l2cap_chan_req_mic_len(chan, conn->hcon);
-			if (smp_conn_security(conn->hcon, sec.level)) {
-				err = -EINVAL;
-				break;
-			}
+		/* SMP receive holds conn->lock; management holds hdev->lock. */
+		hci_dev_lock(conn->hcon->hdev);
+		have_conn = l2cap_chan_lock_conn(chan);
 
-			set_bit(FLAG_PENDING_SECURITY, &chan->flags);
-			sk->sk_state = BT_CONFIG;
-			chan->state = BT_CONFIG;
+		if (l2cap_chan_conn(chan) != conn)
+			err = -ENOTCONN;
+		else
+			err = l2cap_sock_set_security(sk, chan, &sec);
+		l2cap_chan_unlock_conn(chan, have_conn);
+		hci_dev_unlock(conn->hcon->hdev);
+		l2cap_conn_put(conn);
+		return err;
+	}
 
-		/* or for ACL link */
-		} else if ((sk->sk_state == BT_CONNECT2 &&
-			    test_bit(BT_SK_DEFER_SETUP, &bt_sk(sk)->flags)) ||
-			   sk->sk_state == BT_CONNECTED) {
-			if (!l2cap_chan_check_security(chan, true))
-				set_bit(BT_SK_SUSPEND, &bt_sk(sk)->flags);
-			else
-				sk->sk_state_change(sk);
-		} else {
-			err = -EINVAL;
-		}
-		break;
+	l2cap_chan_lock(chan);
+	lock_sock(sk);
+
+	switch (optname) {
 
 	case BT_DEFER_SETUP:
 		if (sk->sk_state != BT_BOUND && sk->sk_state != BT_LISTEN) {
@@ -1284,11 +1318,20 @@ static int l2cap_sock_defer(struct sock *sk)
 		if (!have_conn) {
 			release_sock(sk);
 			err = -ENOTCONN;
+		} else if ((chan->mode == L2CAP_MODE_LE_FLOWCTL ||
+			    chan->mode == L2CAP_MODE_EXT_FLOWCTL) &&
+			   test_bit(FLAG_PENDING_SECURITY, &chan->flags)) {
+			release_sock(sk);
+			err = -EAGAIN;
 		} else if (chan->mode == L2CAP_MODE_EXT_FLOWCTL) {
+			int result;
+
 			sk->sk_state = BT_CONNECTED;
 			chan->state = BT_CONNECTED;
 			release_sock(sk);
-			__l2cap_ecred_conn_rsp_defer(chan);
+			result = __l2cap_ecred_conn_rsp_defer(chan);
+			if (result)
+				err = result;
 		} else if (bdaddr_type_is_le(chan->src_type)) {
 			sk->sk_state = BT_CONNECTED;
 			chan->state = BT_CONNECTED;
@@ -1774,7 +1817,10 @@ static void l2cap_sock_teardown_cb(struct l2cap_chan *chan, int err)
 		sk->sk_state = BT_CLOSED;
 		chan->state = BT_CLOSED;
 
-		sk->sk_err = err;
+		if (!test_bit(FLAG_SECURITY_FAILED, &chan->flags) ||
+		    READ_ONCE(sk->sk_err) != ECONNREFUSED)
+			sk->sk_err = err;
+		clear_bit(BT_SK_SUSPEND, &bt_sk(sk)->flags);
 
 		if (parent) {
 			/*
@@ -1810,8 +1856,11 @@ static void l2cap_sock_state_change_cb(struct l2cap_chan *chan, int state,
 
 	sk->sk_state = state;
 
-	if (err)
+	if (err) {
 		sk->sk_err = err;
+		clear_bit(BT_SK_SUSPEND, &bt_sk(sk)->flags);
+		sk->sk_state_change(sk);
+	}
 
 	release_sock(sk);
 }
diff --git a/net/bluetooth/smp.c b/net/bluetooth/smp.c
index f8f86b8f7..84d85613c 100644
--- a/net/bluetooth/smp.c
+++ b/net/bluetooth/smp.c
@@ -785,6 +785,7 @@ static void smp_failure(struct l2cap_conn *conn, u8 reason)
 			     &reason);
 
 	mgmt_auth_failed(hcon, HCI_ERROR_AUTH_FAILURE);
+	l2cap_security_failure(conn);
 
 	if (chan->data)
 		smp_chan_destroy(conn);


      parent reply	other threads:[~2026-11-13 14:01 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-13  3:06 [PATCH v10 1/2] Bluetooth: L2CAP: Restrict LE credits to connected CoCs Kyle Pepper
2026-11-13  3:06 ` [PATCH v10 2/2] Bluetooth: L2CAP: Keep credits from completing security upgrades Kyle Pepper
2026-11-13 14:00 ` [PATCH v11 1/2] Bluetooth: L2CAP: Restrict LE credits to connected CoCs Kyle Pepper
2026-11-13 14:00 ` Kyle Pepper [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=cfa7a4ee41a18ad131aa77c80e82c86f7854a190.1794550328.git.github.filter194@slmail.me \
    --to=cochcoder@proton.me \
    --cc=linux-bluetooth@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=luiz.dentz@gmail.com \
    --cc=marcel@holtmann.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®