Re: [RFC PATCH v3 1/2] Bluetooth: L2CAP: Keep credits from completing security upgrades
From: cochcoder
Date: Sat Oct 31 2026 - 14:10:17 EST
Hi,
Following up on this patch, is there anything else needed to move it forward?
Thanks,
Kyle
On Thursday, October 15th, 2026 at 2:16 PM, Kyle Pepper <cochcoder@xxxxxxxxx> wrote:
> Credit indications can invoke resume on fixed or unready channels.
> Connected LE credit-based sockets can also resume during a security
> upgrade because credits and security share the socket suspend flag.
>
> Validate the target mode and state for every identifier. Track pending
> LE/ECRED security upgrades and retain credits without sending queued
> data or resuming the socket until encryption meets the requested level.
> Drain queued data after security completes and keep credit-starved
> sockets suspended.
>
> Fixes: fad5fc89594d ("Bluetooth: Add LE flow control discipline")
> Assisted-by: OpenCode:gpt-6.1-sol
> Signed-off-by: Kyle Pepper <cochcoder@xxxxxxxxx>
> ---
> Changes since the standalone security submission:
> - Wrap test notes to fix the GitLint B1 failures in series 1186119.
> - Refresh onto bluetooth-next ff8b09806b7b without a source delta.
> - Include this prerequisite as patch 1/2 of the credit RFC v3 series.
>
> This security fix is independently useful. It does not accept identifier
> zero and can be considered without patch 2. The series uses an RFC prefix
> because patch 2 still requires agreement on a specification exception.
> Do not queue the dependent patch alone before this prerequisite lands.
>
> The saved standalone CI report lists every job except GitLint passing,
> including the native L2CAP tester. This is not a CI result for this series.
>
> Earlier local runs on 397464709a20: nine focused security controls pass
> on security-only and combined guests. The 14 public credit controls pass
> 13/14 security-only (expected positive zero-ID failure), 14/14 combined.
> There were no skips or captured kernel alerts in these focused runs.
> GCC W=1 changed objects and source-extracted fixtures also passed there.
>
> That base's full local attempt was incomplete: SCI Fast Mode timed out
> after an invalid HCI response length for opcode 0x20a3. The same failure
> reproduced unpatched; this patch does not claim to fix it. Initial spaced
> VM filters selected broader tests; corrected space-free focused runs are
> the results above. Historical older-base results of 133/134 and 134/134
> are not fresh full-suite results for this series.
>
> The combined Arch trial is reported working, not isolated security or
> exhaustive lifetime coverage. No severity/CVE claim is made. The private
> reproducer and detailed private regression logs remain excluded.
>
> net/bluetooth/l2cap_core.c | 33 +++++++++++++++++++++++++++++++--
> net/bluetooth/l2cap_sock.c | 9 +++++++--
> 2 files changed, 38 insertions(+), 4 deletions(-)
>
> diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
> index 4dd56e007..7a7f30531 100644
> --- a/net/bluetooth/l2cap_core.c
> +++ b/net/bluetooth/l2cap_core.c
> @@ -5391,6 +5391,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");
> @@ -5404,6 +5409,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);
>
> @@ -7967,6 +7975,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 pending_le;
> +
> l2cap_chan_lock(chan);
>
> lockdep_assert_held(&chan->conn->lock);
> @@ -7974,7 +7984,12 @@ 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)
> + pending_le = hcon->type == LE_LINK &&
> + (chan->mode == L2CAP_MODE_LE_FLOWCTL ||
> + chan->mode == L2CAP_MODE_EXT_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)) {
> @@ -7984,7 +7999,21 @@ static void l2cap_security_cfm(struct hci_conn *hcon, u8 status, u8 encrypt)
>
> if (!status && (chan->state == BT_CONNECTED ||
> chan->state == BT_CONFIG)) {
> - chan->ops->resume(chan);
> + if (pending_le) {
> + if (!encrypt ||
> + !smp_sufficient_security(hcon, chan->sec_level,
> + SMP_ALLOW_STK)) {
> + l2cap_chan_unlock(chan);
> + continue;
> + }
> +
> + clear_bit(FLAG_PENDING_SECURITY, &chan->flags);
> + chan->sec_level = hcon->sec_level;
> + l2cap_le_flowctl_send(chan);
> + }
> +
> + if (!pending_le || chan->tx_credits)
> + chan->ops->resume(chan);
> l2cap_check_encryption(chan, encrypt);
> l2cap_chan_unlock(chan);
> continue;
> diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
> index c396987c5..2d7f2935c 100644
> --- a/net/bluetooth/l2cap_sock.c
> +++ b/net/bluetooth/l2cap_sock.c
> @@ -993,10 +993,15 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname,
> } 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 (!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
> + } else {
> sk->sk_state_change(sk);
> + }
> } else {
> err = -EINVAL;
> }
>
> base-commit: ff8b09806b7b18884ad8d3a2b3d702da534a4e21
>