From: Kyle Pepper <cochcoder@proton.me>
To: linux-bluetooth@vger.kernel.org
Cc: luiz.dentz@gmail.com, marcel@holtmann.org
Subject: [RFC PATCH v9 1/2] Bluetooth: L2CAP: Keep credits from completing security upgrades
Date: Thu, 12 Nov 2026 02:30:38 +0000 [thread overview]
Message-ID: <20261112023025.187702-1-cochcoder@proton.me> (raw)
In-Reply-To: <20261107071634.2237059-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 receive under the connection lock and
with management failures under the device lock. Take the device,
connection, channel and socket locks in order. 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>
---
v9: copy options before locking; reject failed deferred batches before
resume. Rebase on current bluetooth-next, preserving the short security
option ABI and MIC requirements introduced by EKS support.
Previous v8: https://lore.kernel.org/linux-bluetooth/20261107071634.2237059-1-cochcoder@proton.me/
CI checker repair: https://lore.kernel.org/linux-bluetooth/20261112022417.185349-1-cochcoder@proton.me/
CI tester repair: https://lore.kernel.org/linux-bluetooth/20261112022508.185512-1-cochcoder@proton.me/
include/net/bluetooth/l2cap.h | 7 +
net/bluetooth/l2cap_core.c | 235 ++++++++++++++++++++++++++++++++--
net/bluetooth/l2cap_sock.c | 176 ++++++++++++++++---------
net/bluetooth/smp.c | 1 +
4 files changed, 347 insertions(+), 72 deletions(-)
diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
index cf5c2df68..ce5c21f43 100644
--- a/include/net/bluetooth/l2cap.h
+++ b/include/net/bluetooth/l2cap.h
@@ -620,6 +620,7 @@ struct l2cap_chan {
void *data;
const struct l2cap_ops *ops;
struct mutex lock;
+ unsigned int security_generation;
};
struct l2cap_conn {
@@ -645,6 +646,9 @@ struct l2cap_conn {
struct sk_buff_head pending_rx;
struct work_struct pending_rx_work;
bool pending_rx_blocked __guarded_by(&lock);
+ struct work_struct security_work;
+ atomic_t security_failed;
+ atomic_t security_generation;
struct delayed_work id_addr_timer;
@@ -767,6 +771,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 +999,7 @@ 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);
+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 09ff32394..8a2a99084 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);
@@ -705,11 +707,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 */
@@ -877,6 +882,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);
@@ -894,7 +906,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;
}
}
@@ -2711,6 +2727,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--;
@@ -4043,8 +4062,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:
@@ -4052,6 +4076,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 */
@@ -4140,10 +4166,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)
@@ -5464,6 +5496,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");
@@ -5477,6 +5514,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);
@@ -7618,6 +7658,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;
@@ -8070,6 +8112,128 @@ 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) ||
+ !l2cap_check_mic_len(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)
{
@@ -8084,6 +8248,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);
@@ -8091,7 +8257,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)) {
@@ -8099,9 +8271,49 @@ 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) ||
+ !l2cap_check_mic_len(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);
+ /* 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;
+ }
+ if (!le_flowctl || chan->tx_credits)
+ chan->ops->resume(chan);
l2cap_check_encryption(chan, encrypt);
l2cap_chan_unlock(chan);
continue;
@@ -8430,9 +8642,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);
@@ -8449,6 +8667,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 400c167a9..8c63610f1 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)
+ __must_hold(&chan->lock)
+{
+ struct l2cap_conn *conn;
+
+ 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;
+ chan->mic_len = sec->mic_len;
+ 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) {
+ l2cap_chan_req_mic_len(chan, conn->hcon);
+ 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;
@@ -950,79 +997,62 @@ 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;
- }
+ chan->chan_type != L2CAP_CHAN_RAW)
+ return -EINVAL;
- /* 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);
conn = l2cap_chan_conn(chan);
- if (!conn)
- break;
+ if (!conn) {
+ lock_sock(sk);
+ err = l2cap_sock_set_security(sk, chan, &sec);
+ 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) {
- 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;
+ lock_sock(sk);
+ if (l2cap_chan_conn(chan) != conn)
+ err = -ENOTCONN;
+ else
+ err = l2cap_sock_set_security(sk, chan, &sec);
+ 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;
@@ -1284,11 +1314,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;
@@ -1393,7 +1435,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);
@@ -1758,6 +1800,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:
@@ -1774,7 +1817,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) {
/*
@@ -1810,8 +1855,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 f8f86b8f7..0c52a819d 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);
next prev parent reply other threads:[~2026-11-12 2:30 UTC|newest]
Thread overview: 27+ 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 ` [RFC PATCH v8 1/2] Bluetooth: L2CAP: Keep credits from completing security upgrades Kyle Pepper
2026-11-07 7:17 ` [RFC PATCH v8 2/2] Bluetooth: L2CAP: tolerate zero-ident LE credit indications Kyle Pepper
2026-11-07 8:32 ` [RFC,v8,1/2] Bluetooth: L2CAP: Keep credits from completing security upgrades bluez.test.bot
2026-11-12 2:30 ` Kyle Pepper [this message]
2026-11-12 2:30 ` [RFC PATCH v9 2/2] Bluetooth: L2CAP: tolerate zero-ident LE credit indications Kyle Pepper
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=20261112023025.187702-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