mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v10 1/2] Bluetooth: L2CAP: Restrict LE credits to connected CoCs
@ 2026-11-13  3:06 Kyle Pepper
  2026-11-13  3:06 ` [PATCH v10 2/2] Bluetooth: L2CAP: Keep credits from completing security upgrades Kyle Pepper
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: Kyle Pepper @ 2026-11-13  3:06 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: luiz.dentz, marcel, linux-kernel

Do not let credit indications change fixed channels or complete pending
connection setup. Validate the channel state and mode before accounting
credits, checking overflow or invoking its resume callback.

Fixes: fad5fc89594d ("Bluetooth: Add LE flow control discipline")
Assisted-by: LLM
Signed-off-by: Kyle Pepper <cochcoder@proton.me>
---
Hi Luiz,

I split the credit validation into its own patch and reworked the security
handling to use the existing HCI workqueue. There is no new workqueue or
pairing timeout. The original l2cap-tester and smp-tester now pass without
changing their expectations.

I also checked failure handling, repeated security
requests, key refresh and deferred LE/ECRED setup so fixing that bypass
doesn't leave sockets stuck.

Hopefully this brings the patch to a more usable state.

Thanks,
Kyle

v10:
- Separate malformed-credit validation from security request completion.
- Use the existing HCI workqueue and SMP/HCI timeouts.
- Keep pending upgrades gated through failure, refresh and deferred setup.

Previous v9: https://lore.kernel.org/linux-bluetooth/20261112022948.187618-1-cochcoder@proton.me/
Threading uses the Message-ID from sent mail; the archive lookup is not yet
available. Luiz's reply Message-ID was not supplied.

Validation: original l2cap-tester 111/111 and smp-tester 8/8; 27 private native
cases; GCC/Clang/ECRED-off W=1 builds; 42 modeled cases per compiler with
ASan/UBSan and 10 expected mutation failures. Guest tests use emulated
controllers and checked module identities. Physical hardware, PTS, KCSAN
and remote CI were not run. Private fixtures and traces are not included.

 net/bluetooth/l2cap_core.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index 09ff32394..4e8aec98e 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -5464,6 +5464,11 @@ static inline int l2cap_le_credits(struct l2cap_conn *conn,
 
 	l2cap_chan_lock(chan);
 
+	if (chan->state != BT_CONNECTED ||
+	    (chan->mode != L2CAP_MODE_LE_FLOWCTL &&
+	     chan->mode != L2CAP_MODE_EXT_FLOWCTL))
+		goto unlock;
+
 	max_credits = LE_FLOWCTL_MAX_CREDITS - chan->tx_credits;
 	if (credits > max_credits) {
 		BT_ERR("LE credits overflow");

base-commit: da3cc02587c629420603df4395179ec82955c941


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH v10 2/2] Bluetooth: L2CAP: Keep credits from completing security upgrades
  2026-11-13  3:06 [PATCH v10 1/2] Bluetooth: L2CAP: Restrict LE credits to connected CoCs Kyle Pepper
@ 2026-11-13  3:06 ` 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 ` [PATCH v11 2/2] Bluetooth: L2CAP: Keep credits from completing security upgrades Kyle Pepper
  2 siblings, 0 replies; 4+ messages in thread
From: Kyle Pepper @ 2026-11-13  3:06 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: luiz.dentz, marcel, linux-kernel

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, and fail pending
requests even if a later confirmation would otherwise satisfy them.
Use the existing pairing and HCI timeouts without a new request deadline.

Drain retained data when security completes or the requested requirement
is lowered to an achieved level. Return EACCES and restore the old request
when SMP starts no procedure and the requirements remain unmet. 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 and reject the batch on security
failure. Preserve ordinary deferred rejection behavior.

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

diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
index cf5c2df68..7b95bd407 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,
 };
@@ -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..3736eda47 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 */
@@ -2705,12 +2710,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 +2733,192 @@ 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->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)) {
+			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 (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;
+	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;
+
+	chan->sec_level = level;
+	chan->mic_len = mic_len;
+	if (test_bit(FLAG_PENDING_SECURITY, &chan->flags)) {
+		if (!l2cap_chan_security_ready(chan)) {
+			err = -EBUSY;
+			goto restore;
+		}
+	} else 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;
+	return err;
 }
 
 static void l2cap_tx_timestamp(struct sk_buff *skb,
@@ -2791,9 +2991,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 +4241,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)
 		return;
 
 	switch (chan->state) {
@@ -4052,6 +4257,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 +4269,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 +4349,16 @@ 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 {
+		l2cap_chan_del(chan, rsp->security_failed ?
+			      ECONNREFUSED : ECONNRESET);
+		if (rsp->security_failed && chan != rsp->locked_chan)
+			chan->ops->close(chan);
+	}
 
 done:
 	if (locked)
@@ -4156,8 +4371,10 @@ void __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;
@@ -4169,6 +4386,13 @@ void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan)
 	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);
@@ -4190,6 +4414,22 @@ 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);
+	}
 }
 
 void __l2cap_connect_rsp_defer(struct l2cap_chan *chan)
@@ -5483,10 +5723,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 +7860,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 +8328,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 +8337,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 +8348,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..ad38fba6a 100644
--- a/net/bluetooth/l2cap_sock.c
+++ b/net/bluetooth/l2cap_sock.c
@@ -929,12 +929,59 @@ 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) {
+		l2cap_chan_req_mic_len(chan, conn->hcon);
+		if (smp_conn_security(conn->hcon, sec->level)) {
+			err = -EINVAL;
+			goto done;
+		}
+		set_bit(FLAG_PENDING_SECURITY, &chan->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) {
+		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;
+	}
+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 +997,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);
-
-	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;
-		}
+	if (optname == BT_SECURITY) {
+		struct bt_security sec = { .level = BT_SECURITY_LOW };
+		bool have_conn;
 
-		/* 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,6 +1312,11 @@ 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) {
 			sk->sk_state = BT_CONNECTED;
 			chan->state = BT_CONNECTED;
@@ -1774,7 +1807,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 +1846,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);


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH v11 1/2] Bluetooth: L2CAP: Restrict LE credits to connected CoCs
  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 ` Kyle Pepper
  2026-11-13 14:00 ` [PATCH v11 2/2] Bluetooth: L2CAP: Keep credits from completing security upgrades Kyle Pepper
  2 siblings, 0 replies; 4+ messages in thread
From: Kyle Pepper @ 2026-11-13 14:00 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: luiz.dentz, marcel, linux-kernel

Do not let credit indications change fixed channels or complete pending
connection setup. Validate the channel state and mode before accounting
credits, checking overflow or invoking its resume callback.

Fixes: fad5fc89594d ("Bluetooth: Add LE flow control discipline")
Assisted-by: LLM
Signed-off-by: Kyle Pepper <cochcoder@proton.me>
---
Changes in v11:
- Purge retained deferred CoC data before incomplete-configuration teardown.
- Clear pending work flags on close and recheck channel state in the worker.
- Return EBUSY for overlapping SMP/encryption requests, including key
  distribution; restore pending_sec_level when rejecting an idle request.
- Complete ECRED child cleanup and propagate deferred security rejection.
- Preserve ordinary accept/close behavior and socket/SMP lock ordering.

Previous v10 (this series has no cover letter):
https://lore.kernel.org/linux-bluetooth/20261113030556.75484-1-cochcoder@proton.me/

 net/bluetooth/l2cap_core.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index 09ff32394..4e8aec98e 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -5464,6 +5464,11 @@ static inline int l2cap_le_credits(struct l2cap_conn *conn,
 
 	l2cap_chan_lock(chan);
 
+	if (chan->state != BT_CONNECTED ||
+	    (chan->mode != L2CAP_MODE_LE_FLOWCTL &&
+	     chan->mode != L2CAP_MODE_EXT_FLOWCTL))
+		goto unlock;
+
 	max_credits = LE_FLOWCTL_MAX_CREDITS - chan->tx_credits;
 	if (credits > max_credits) {
 		BT_ERR("LE credits overflow");

base-commit: da3cc02587c629420603df4395179ec82955c941


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH v11 2/2] Bluetooth: L2CAP: Keep credits from completing security upgrades
  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
  2 siblings, 0 replies; 4+ messages in thread
From: Kyle Pepper @ 2026-11-13 14:00 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: luiz.dentz, marcel, linux-kernel

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);


^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-11-13 14:01 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
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 ` [PATCH v11 2/2] Bluetooth: L2CAP: Keep credits from completing security upgrades Kyle Pepper

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®