Linux bluetooth development
 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
Subject: [RFC PATCH v8 1/2] Bluetooth: L2CAP: Keep credits from completing security upgrades
Date: Sat, 07 Nov 2026 07:16:51 +0000	[thread overview]
Message-ID: <20261107071634.2237059-1-cochcoder@proton.me> (raw)
In-Reply-To: <20261107023556.1475788-1-cochcoder@proton.me>

Retain LE and ECRED credits without sending queued data or resuming
connected sockets while a security upgrade is pending. Guard the shared
send path and deferred acceptance, and preserve the requested level.

Finish pending upgrades from referenced connection work outside the
security callback's list walk. Keep failures terminal across later
confirmations and notify pending channels when local SMP pairing fails.
Use a dedicated unbound queue so management replies can report failure
while the device queue drains. Drain it before L2CAP exits and release
it if socket initialization fails. Ignore detached or powered-down links.

Serialize BT_SECURITY with SMP failure under the device lock, followed
by the connection, channel and socket locks. Record a failure generation
on each request so delayed failure work leaves newer requests alone.
Publish the failing generation in the consumed atomic and compare request
generations with signed subtraction. Skip zero when the counter wraps.
Keep EBUSY for channels with their own upgrade already pending.

HCI confirmations describe the current ACL link. A failed encryption event
can disconnect it, so pending requests still fail regardless of their
local SMP generation. The powered-down check is advisory; HCI separately
guards transmit admission while draining and flushes connections on close.

Check every deferred ECRED sibling before responding. Reject the whole
batch on failure even if a child has already become BT_CONNECTED while
waiting for the response. Include a zero destination CID for each refused
channel. Close rejected children under a temporary reference, using child
socket lock nesting, so unaccepted sockets release their ownership.
Normal incoming ECRED channels enter BT_CONNECT2 only with deferred setup.
For a synthetic child without that flag or an identifier, delete locally;
there is no pending deferred response to send. Choose this fallback before
batch handling, avoiding a channel read after rejected children close.

Wake deferred sockets after successful security even with no initial
credits. Consume and return deferred batch errors. Preserve a pending
security rejection through teardown without masking unrelated socket
errors, and clear security and suspension state when removing a channel.

Assisted-by: LLM
Signed-off-by: Kyle Pepper <cochcoder@proton.me>
---
Changes since v7: defer rejection of an unencrypted deferred ECRED batch
from the security callback to the security workqueue.

Validation: mocked source-level tests pass; native v8 testing, hardware,
KCSAN, PTS and remote CI remain unverified. Leak detection did not run.

Previous version: https://lore.kernel.org/linux-bluetooth/20261107023556.1475788-1-cochcoder@proton.me/

 include/net/bluetooth/l2cap.h |   7 +
 net/bluetooth/l2cap_core.c    | 233 ++++++++++++++++++++++++++++++++--
 net/bluetooth/l2cap_sock.c    | 164 ++++++++++++++++--------
 net/bluetooth/smp.c           |   1 +
 4 files changed, 344 insertions(+), 61 deletions(-)

diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
index 2a05bda8a..4aba89900 100644
--- a/include/net/bluetooth/l2cap.h
+++ b/include/net/bluetooth/l2cap.h
@@ -612,6 +612,7 @@ struct l2cap_chan {
 	void			*data;
 	const struct l2cap_ops	*ops;
 	struct mutex		lock;
+	unsigned int		security_generation;
 };
 
 struct l2cap_conn {
@@ -636,6 +637,9 @@ struct l2cap_conn {
 
 	struct sk_buff_head	pending_rx;
 	struct work_struct	pending_rx_work;
+	struct work_struct	security_work;
+	atomic_t		security_failed;
+	atomic_t		security_generation;
 
 	struct delayed_work	id_addr_timer;
 
@@ -758,6 +762,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,
 };
@@ -983,6 +989,7 @@ int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len,
 void l2cap_chan_busy(struct l2cap_chan *chan, int busy);
 void l2cap_chan_rx_avail(struct l2cap_chan *chan, ssize_t rx_avail);
 int l2cap_chan_check_security(struct l2cap_chan *chan, bool initiator);
+void l2cap_security_failed(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 7dcb0586a..3a7b71321 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -59,6 +59,8 @@ 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 struct workqueue_struct *l2cap_security_wq;
 static void __l2cap_chan_close(struct l2cap_chan *chan, int reason)
 	__must_hold(&chan->lock) __must_hold(&chan->conn->lock);
 
@@ -704,11 +706,14 @@ void l2cap_chan_del(struct l2cap_chan *chan, int err)
 	lockdep_assert(!chan->conn || lockdep_is_held(&chan->conn->lock));
 
 	__clear_chan_timer(chan);
+	clear_bit(FLAG_PENDING_SECURITY, &chan->flags);
+	clear_bit(FLAG_SECURITY_CFM, &chan->flags);
 
 	BT_DBG("chan %p, err %d, state %s", chan, err,
 	       state_to_string(chan->state));
 
 	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 */
@@ -876,6 +881,13 @@ static void __l2cap_chan_close(struct l2cap_chan *chan, int reason)
 
 	case BT_CONNECTED:
 	case BT_CONFIG:
+		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);
+			break;
+		}
+
 		if (chan->chan_type == L2CAP_CHAN_CONN_ORIENTED) {
 			__set_chan_timer(chan, chan->ops->get_sndtimeo(chan));
 			l2cap_send_disconn_req(chan, reason);
@@ -893,7 +905,11 @@ static void __l2cap_chan_close(struct l2cap_chan *chan, int reason)
 					l2cap_chan_le_connect_reject(chan);
 					break;
 				case L2CAP_MODE_EXT_FLOWCTL:
-					l2cap_chan_ecred_connect_reject(chan);
+					if (chan->ident &&
+					    test_bit(FLAG_DEFER_SETUP, &chan->flags))
+						l2cap_chan_ecred_connect_reject(chan);
+					else
+						l2cap_chan_del(chan, reason);
 					return;
 				}
 			}
@@ -2647,6 +2663,9 @@ static void l2cap_le_flowctl_send(struct l2cap_chan *chan)
 
 	BT_DBG("chan %p", chan);
 
+	if (test_bit(FLAG_PENDING_SECURITY, &chan->flags))
+		return;
+
 	while (chan->tx_credits && !skb_queue_empty(&chan->tx_q)) {
 		l2cap_do_send(chan, skb_dequeue(&chan->tx_q));
 		chan->tx_credits--;
@@ -3979,8 +3998,13 @@ 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 (*result < 0 || test_bit(FLAG_ECRED_CONN_REQ_SENT, &chan->flags))
+		return;
+
+	if (test_bit(FLAG_SECURITY_FAILED, &chan->flags)) {
+		*result = -ECONNREFUSED;
 		return;
+	}
 
 	switch (chan->state) {
 	case BT_CONNECT2:
@@ -3988,6 +4012,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 */
@@ -4076,10 +4102,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_flex->dcid[rsp->count++] = cpu_to_le16(chan->scid);
-	else
-		l2cap_chan_del(chan, ECONNRESET);
+	} else {
+		if (rsp->count < ARRAY_SIZE(rsp->pdu.scid))
+			rsp_flex->dcid[rsp->count++] = 0;
+		l2cap_chan_del(chan, rsp->pdu.rsp.result ==
+			      cpu_to_le16(L2CAP_CR_LE_AUTHORIZATION) ?
+			      ECONNREFUSED : ECONNRESET);
+		chan->ops->close(chan);
+	}
 
 done:
 	if (locked)
@@ -5380,6 +5412,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");
@@ -5393,6 +5430,9 @@ static inline int l2cap_le_credits(struct l2cap_conn *conn,
 
 	chan->tx_credits += credits;
 
+	if (test_bit(FLAG_PENDING_SECURITY, &chan->flags))
+		goto unlock;
+
 	/* Resume sending */
 	l2cap_le_flowctl_send(chan);
 
@@ -7510,6 +7550,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_failed, 0);
 	INIT_DELAYED_WORK(&conn->id_addr_timer, l2cap_conn_update_id_addr);
 
 	conn->disc_reason = HCI_ERROR_REMOTE_USER_TERM;
@@ -7962,6 +8004,127 @@ static inline void l2cap_check_encryption(struct l2cap_chan *chan, u8 encrypt)
 	}
 }
 
+static void l2cap_queue_security(struct l2cap_conn *conn)
+{
+	l2cap_conn_get(conn);
+	/* Management process replies can fail SMP while the device queue drains;
+	 * this work cannot depend on admission to that draining queue.
+	 */
+	if (!queue_work(l2cap_security_wq, &conn->security_work))
+		l2cap_conn_put(conn);
+}
+
+void l2cap_security_failed(struct l2cap_conn *conn)
+{
+	unsigned int generation;
+
+	if (conn->hcon->type != LE_LINK)
+		return;
+
+	/* SMP producers serialize on the fixed channel lock. */
+	lockdep_assert_held(&conn->smp->lock);
+
+	/* Zero means no failure queued, including after generation wrap. */
+	generation = atomic_inc_return(&conn->security_generation);
+	if (!generation)
+		generation = atomic_inc_return(&conn->security_generation);
+	atomic_set(&conn->security_failed, generation);
+	l2cap_queue_security(conn);
+}
+
+static void l2cap_security_work(struct work_struct *work)
+{
+	struct l2cap_conn *conn = container_of(work, struct l2cap_conn,
+					    security_work);
+	struct hci_conn *hcon = conn->hcon;
+	struct l2cap_chan *chan, *next;
+	unsigned int generation;
+
+	hci_dev_lock(hcon->hdev);
+	/* HCI_UP is advisory; device close flushes connections after clearing it.
+	 * TX admission during drain is guarded separately by hci_sched_tx().
+	 */
+	if (hcon->l2cap_data != conn || !test_bit(HCI_UP, &hcon->hdev->flags))
+		goto done;
+
+	mutex_lock(&conn->lock);
+
+	generation = atomic_xchg(&conn->security_failed, 0);
+	if (generation) {
+		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) &&
+			    (s32)(generation - chan->security_generation) > 0) {
+				set_bit(FLAG_SECURITY_FAILED, &chan->flags);
+				set_bit(FLAG_SECURITY_CFM, &chan->flags);
+			}
+			l2cap_chan_unlock(chan);
+		}
+	}
+
+	for (;;) {
+		chan = NULL;
+		list_for_each_entry(next, &conn->chan_l, list) {
+			if (test_and_clear_bit(FLAG_SECURITY_CFM, &next->flags)) {
+				chan = next;
+				break;
+			}
+		}
+		if (!chan)
+			break;
+
+		/* Batch rejection may delete siblings, so restart the walk. */
+		l2cap_chan_hold(chan);
+		l2cap_chan_lock(chan);
+		lockdep_assert_held(&chan->conn->lock);
+		if (!test_bit(FLAG_PENDING_SECURITY, &chan->flags) ||
+		    (chan->state != BT_CONNECTED && chan->state != BT_CONFIG &&
+		     chan->state != BT_CONNECT2))
+			goto next;
+
+		if (test_bit(FLAG_SECURITY_FAILED, &chan->flags) ||
+		    !test_bit(HCI_CONN_ENCRYPT, hcon->flags) ||
+		    !smp_sufficient_security(hcon, chan->sec_level, SMP_ALLOW_STK) ||
+		    !l2cap_check_enc_key_size(hcon, chan)) {
+			set_bit(FLAG_SECURITY_FAILED, &chan->flags);
+			__l2cap_chan_close(chan, ECONNREFUSED);
+			chan->ops->close(chan);
+			goto next;
+		}
+
+		clear_bit(FLAG_PENDING_SECURITY, &chan->flags);
+		chan->sec_level = hcon->sec_level;
+
+		if (chan->state == BT_CONNECT2) {
+			chan->ops->resume(chan);
+			goto next;
+		}
+
+		if (chan->state == BT_CONNECTED &&
+		    chan->mode == L2CAP_MODE_EXT_FLOWCTL &&
+		    test_bit(FLAG_DEFER_SETUP, &chan->flags)) {
+			__l2cap_ecred_conn_rsp_defer(chan);
+			if (test_bit(FLAG_DEL, &chan->flags))
+				goto next;
+		}
+
+		l2cap_le_flowctl_send(chan);
+		if (chan->tx_credits)
+			chan->ops->resume(chan);
+		l2cap_check_encryption(chan, 1);
+next:
+		l2cap_chan_unlock(chan);
+		l2cap_chan_put(chan);
+	}
+
+	mutex_unlock(&conn->lock);
+done:
+	hci_dev_unlock(hcon->hdev);
+	l2cap_conn_put(conn);
+}
+
 static void l2cap_security_cfm(struct hci_conn *hcon, u8 status, u8 encrypt)
 	__must_hold(&hcon->hdev->lock)
 {
@@ -7976,6 +8139,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, pending_le;
+
 		l2cap_chan_lock(chan);
 
 		lockdep_assert_held(&chan->conn->lock);
@@ -7983,7 +8148,13 @@ 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);
+		pending_le = le_flowctl &&
+			     test_bit(FLAG_PENDING_SECURITY, &chan->flags);
+
+		if (!status && encrypt && !pending_le)
 			chan->sec_level = hcon->sec_level;
 
 		if (!__l2cap_no_conn_pending(chan)) {
@@ -7991,9 +8162,48 @@ static void l2cap_security_cfm(struct hci_conn *hcon, u8 status, u8 encrypt)
 			continue;
 		}
 
+		/* HCI reports the current link state. Encryption failure can
+		 * disconnect that link, regardless of the SMP request generation.
+		 */
+		if (pending_le && (chan->state == BT_CONNECTED ||
+				   chan->state == BT_CONFIG ||
+				   chan->state == BT_CONNECT2)) {
+			if (status || !encrypt ||
+			    !smp_sufficient_security(hcon, chan->sec_level,
+						    SMP_ALLOW_STK) ||
+			    !l2cap_check_enc_key_size(hcon, chan)) {
+				set_bit(FLAG_SECURITY_FAILED, &chan->flags);
+			}
+			set_bit(FLAG_SECURITY_CFM, &chan->flags);
+			l2cap_queue_security(conn);
+			l2cap_chan_unlock(chan);
+			continue;
+		}
+
 		if (!status && (chan->state == BT_CONNECTED ||
 				chan->state == BT_CONFIG)) {
-			chan->ops->resume(chan);
+			if (!le_flowctl || chan->tx_credits)
+				chan->ops->resume(chan);
+			/* A deferred multi-channel ECRED connection may have
+			 * released one accepted channel into BT_CONNECTED while
+			 * its batch still shares an ident with pending siblings.
+			 * Its security-failure close rejects that whole batch and
+			 * unlinks those channels from conn->chan_l; do not run it
+			 * directly while this traversal holds chan and the list.
+			 */
+			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) &&
+			    (chan->sec_level == BT_SECURITY_HIGH ||
+			     chan->sec_level == BT_SECURITY_FIPS) &&
+			    !encrypt) {
+				set_bit(FLAG_PENDING_SECURITY, &chan->flags);
+				set_bit(FLAG_SECURITY_FAILED, &chan->flags);
+				set_bit(FLAG_SECURITY_CFM, &chan->flags);
+				l2cap_queue_security(conn);
+				l2cap_chan_unlock(chan);
+				continue;
+			}
 			l2cap_check_encryption(chan, encrypt);
 			l2cap_chan_unlock(chan);
 			continue;
@@ -8321,9 +8531,15 @@ int __init l2cap_init(void)
 {
 	int err;
 
+	l2cap_security_wq = alloc_workqueue("l2cap_sec", WQ_UNBOUND, 0);
+	if (!l2cap_security_wq)
+		return -ENOMEM;
+
 	err = l2cap_init_sockets();
-	if (err < 0)
+	if (err < 0) {
+		destroy_workqueue(l2cap_security_wq);
 		return err;
+	}
 
 	hci_register_cb(&l2cap_cb);
 
@@ -8340,6 +8556,7 @@ void l2cap_exit(void)
 {
 	debugfs_remove(l2cap_debugfs);
 	hci_unregister_cb(&l2cap_cb);
+	destroy_workqueue(l2cap_security_wq);
 	l2cap_cleanup_sockets();
 }
 
diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
index 7a2551ef7..89da73dd3 100644
--- a/net/bluetooth/l2cap_sock.c
+++ b/net/bluetooth/l2cap_sock.c
@@ -927,12 +927,71 @@ 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,
+				   sockptr_t optval, unsigned int optlen)
+	__must_hold(&chan->lock)
+{
+	struct l2cap_conn *conn;
+	struct bt_security sec = { .level = BT_SECURITY_LOW };
+	int err;
+
+	if (chan->chan_type != L2CAP_CHAN_CONN_ORIENTED &&
+	    chan->chan_type != L2CAP_CHAN_FIXED &&
+	    chan->chan_type != L2CAP_CHAN_RAW)
+		return -EINVAL;
+
+	err = copy_safe_from_sockptr(&sec, sizeof(sec), optval, optlen);
+	if (err)
+		return err;
+
+	if (sec.level < BT_SECURITY_LOW || sec.level > BT_SECURITY_FIPS)
+		return -EINVAL;
+
+	conn = l2cap_chan_conn(chan);
+	if ((chan->mode == L2CAP_MODE_LE_FLOWCTL ||
+	     chan->mode == L2CAP_MODE_EXT_FLOWCTL) &&
+	    test_bit(FLAG_PENDING_SECURITY, &chan->flags))
+		return -EBUSY;
+
+	chan->sec_level = sec.level;
+	if (!conn)
+		return 0;
+
+	lockdep_assert_held(&conn->lock);
+	lockdep_assert_held(&conn->hcon->hdev->lock);
+	chan->security_generation = atomic_read(&conn->security_generation);
+
+	if (chan->scid == L2CAP_CID_ATT) {
+		if (smp_conn_security(conn->hcon, sec.level))
+			return -EINVAL;
+
+		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)) {
+			if (conn->hcon->type == LE_LINK &&
+			    (chan->mode == L2CAP_MODE_LE_FLOWCTL ||
+			     chan->mode == L2CAP_MODE_EXT_FLOWCTL))
+				set_bit(FLAG_PENDING_SECURITY, &chan->flags);
+			set_bit(BT_SK_SUSPEND, &bt_sk(sk)->flags);
+		} else {
+			sk->sk_state_change(sk);
+		}
+	} else {
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
 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;
@@ -948,60 +1007,41 @@ 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;
-		}
-
-		sec.level = BT_SECURITY_LOW;
-
-		err = copy_safe_from_sockptr(&sec, sizeof(sec), optval, optlen);
-		if (err)
-			break;
-
-		if (sec.level < BT_SECURITY_LOW ||
-		    sec.level > BT_SECURITY_FIPS) {
-			err = -EINVAL;
-			break;
-		}
-
-		chan->sec_level = sec.level;
+	if (optname == BT_SECURITY) {
+		bool have_conn;
 
+		l2cap_chan_lock(chan);
 		conn = l2cap_chan_conn(chan);
-		if (!conn)
-			break;
+		if (!conn) {
+			lock_sock(sk);
+			err = l2cap_sock_set_security(sk, chan, optval, optlen);
+			release_sock(sk);
+			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) {
-			if (smp_conn_security(conn->hcon, sec.level)) {
-				err = -EINVAL;
-				break;
-			}
+		/* Match SMP failure's hdev -> conn -> chan lock order. */
+		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;
+		lock_sock(sk);
+		if (l2cap_chan_conn(chan) != conn)
+			err = -ENOTCONN;
+		else
+			err = l2cap_sock_set_security(sk, chan, optval, optlen);
+		release_sock(sk);
+		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) {
 			err = -EINVAL;
@@ -1263,11 +1303,23 @@ 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;
 			release_sock(sk);
 			__l2cap_ecred_conn_rsp_defer(chan);
+			lock_sock(sk);
+			if (sk->sk_state == BT_CLOSED) {
+				err = sock_error(sk);
+				if (!err)
+					err = -ECONNRESET;
+			}
+			release_sock(sk);
 		} else if (bdaddr_type_is_le(chan->src_type)) {
 			sk->sk_state = BT_CONNECTED;
 			chan->state = BT_CONNECTED;
@@ -1371,7 +1423,7 @@ static void l2cap_sock_kill(struct sock *sk)
 	/* Take lock to synchronize against access without owning sk->sk_socket,
 	 * eg. in l2cap_sock_cleanup_listen(). proto_ops etc. don't need lock.
 	 */
-	lock_sock(sk);
+	lock_sock_nested(sk, L2CAP_NESTING_NORMAL);
 	l2cap_sock_put_chan(sk);
 	release_sock(sk);
 
@@ -1736,6 +1788,7 @@ static void l2cap_sock_teardown_cb(struct l2cap_chan *chan, int err)
 	lock_sock_nested(sk, atomic_read(&chan->nesting));
 
 	parent = bt_sk(sk)->parent;
+	clear_bit(BT_SK_SUSPEND, &bt_sk(sk)->flags);
 
 	switch (chan->state) {
 	case BT_OPEN:
@@ -1752,7 +1805,9 @@ 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)
+			WRITE_ONCE(sk->sk_err, err);
 
 		if (parent) {
 			/*
@@ -1788,8 +1843,11 @@ static void l2cap_sock_state_change_cb(struct l2cap_chan *chan, int state,
 
 	sk->sk_state = state;
 
-	if (err)
-		sk->sk_err = err;
+	if (err) {
+		WRITE_ONCE(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 b7015e37e..b4b761788 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_failed(conn);
 
 	if (chan->data)
 		smp_chan_destroy(conn);
-- 
2.56.0



  parent reply	other threads:[~2026-11-07  7:16 UTC|newest]

Thread overview: 25+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-13  3:19 [RFC PATCH] Bluetooth: L2CAP: tolerate zero-ident LE credit indications Kyle Pepper
2026-10-13  4:20 ` [RFC] " bluez.test.bot
2026-10-13 14:43 ` [RFC PATCH] " Luiz Augusto von Dentz
2026-10-15 18:31   ` cochcoder
2026-10-15  5:28 ` [RFC PATCH v2] " Kyle Pepper
2026-10-15  6:42   ` [RFC,v2] " bluez.test.bot
2026-10-15 18:16   ` [RFC PATCH v3 1/2] Bluetooth: L2CAP: Keep credits from completing security upgrades Kyle Pepper
2026-10-15 18:17     ` [RFC PATCH v3 2/2] Bluetooth: L2CAP: tolerate zero-ident LE credit indications Kyle Pepper
2026-10-15 20:17     ` [RFC,v3,1/2] Bluetooth: L2CAP: Keep credits from completing security upgrades bluez.test.bot
2026-10-31 18:09     ` [RFC PATCH v3 1/2] " cochcoder
2026-11-06  2:17     ` [RFC PATCH v4 " Kyle Pepper
2026-11-06  2:18       ` [RFC PATCH v4 2/2] Bluetooth: L2CAP: tolerate zero-ident LE credit indications Kyle Pepper
2026-11-06  3:17       ` [RFC,v4,1/2] Bluetooth: L2CAP: Keep credits from completing security upgrades bluez.test.bot
2026-11-06  4:55       ` [RFC PATCH v5 1/2] " Kyle Pepper
2026-11-06  4:56         ` [RFC PATCH v5 2/2] Bluetooth: L2CAP: tolerate zero-ident LE credit indications Kyle Pepper
2026-11-06 16:30           ` Luiz Augusto von Dentz
2026-11-06 10:40         ` [RFC,v5,1/2] Bluetooth: L2CAP: Keep credits from completing security upgrades bluez.test.bot
2026-11-06 17:40         ` [RFC PATCH v6 1/2] " Kyle Pepper
2026-11-06 17:40           ` [RFC PATCH v6 2/2] Bluetooth: L2CAP: tolerate zero-ident LE credit indications Kyle Pepper
2026-11-06 22:54           ` [RFC,v6,1/2] Bluetooth: L2CAP: Keep credits from completing security upgrades bluez.test.bot
2026-11-07  2:36           ` [RFC PATCH v7 1/2] " Kyle Pepper
2026-11-07  2:36             ` [RFC PATCH v7 2/2] Bluetooth: L2CAP: tolerate zero-ident LE credit indications Kyle Pepper
2026-11-07  7:16             ` Kyle Pepper [this message]
2026-11-07  7:17               ` [RFC PATCH v8 " Kyle Pepper
2026-11-07  8:32               ` [RFC,v8,1/2] Bluetooth: L2CAP: Keep credits from completing security upgrades bluez.test.bot

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=20261107071634.2237059-1-cochcoder@proton.me \
    --to=cochcoder@proton.me \
    --cc=linux-bluetooth@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