* [RFC PATCH] Bluetooth: L2CAP: tolerate zero-ident LE credit indications
@ 2026-10-13 3:19 Kyle Pepper
2026-10-13 4:20 ` [RFC] " bluez.test.bot
` (2 more replies)
0 siblings, 3 replies; 25+ messages in thread
From: Kyle Pepper @ 2026-10-13 3:19 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Marcel Holtmann, Luiz Augusto von Dentz, linux-kernel
A nonconforming ASHA peer sends identifier-zero credit indications
that are discarded, reducing usable transmit credits until audio stalls.
Tolerate only exact-size credit indications for a connected LE CoC.
Preserve the channel reference, lock and credit-overflow checks and do
not send a response with identifier zero. Other zero-ID commands and
BR/EDR signaling remain unchanged.
Core Vol 3 Part A section 4 forbids identifier zero. This is an
interoperability RFC requiring maintainer agreement, not a conformance
fix or a claim of automatic stream recovery.
Assisted-by: OpenCode:gpt-6.1-sol
Signed-off-by: Kyle Pepper <cochcoder@proton.me>
---
Submission review notes (not part of the commit message):
Core specification forbids identifier zero. This is a narrow opcode
interoperability exception, not a conformance fix. N8 captures contain
zero-ID credit indications. Native malformed/overflow/teardown and
lockdep/KASAN coverage and exception policy remain review gates.
net/bluetooth/l2cap_core.c | 18 +++++++++++++++++-
1 file changed, 17 insertions(+), 1 deletion(-)
diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index 49a998804..1a0868437 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -5328,6 +5328,12 @@ static inline int l2cap_le_credits(struct l2cap_conn *conn,
l2cap_chan_lock(chan);
+ /* Tolerate identifier-zero credits only for a live LE CoC. */
+ if (!cmd->ident &&
+ (!credits || chan->state != BT_CONNECTED ||
+ chan->mode != L2CAP_MODE_LE_FLOWCTL))
+ goto unlock;
+
max_credits = LE_FLOWCTL_MAX_CREDITS - chan->tx_credits;
if (credits > max_credits) {
BT_ERR("LE credits overflow");
@@ -5923,11 +5929,21 @@ static inline void l2cap_le_sig_channel(struct l2cap_conn *conn,
BT_DBG("code 0x%2.2x len %d id 0x%2.2x", cmd->code, len, cmd->ident);
- if (len != skb->len || !cmd->ident) {
+ if (len != skb->len) {
BT_DBG("corrupted command");
goto drop;
}
+ if (!cmd->ident) {
+ if (cmd->code != L2CAP_LE_CREDITS ||
+ len != sizeof(struct l2cap_le_credits))
+ goto drop;
+
+ /* Credits have no response or identifier-matched transaction. */
+ l2cap_le_credits(conn, cmd, len, skb->data);
+ goto drop;
+ }
+
err = l2cap_le_sig_cmd(conn, cmd, len, skb->data);
if (err) {
struct l2cap_cmd_rej_unk rej;
base-commit: 964430c038967fd06643e866f68115664e1a1bd1
^ permalink raw reply related [flat|nested] 25+ messages in thread* RE: [RFC] Bluetooth: L2CAP: tolerate zero-ident LE credit indications 2026-10-13 3:19 [RFC PATCH] Bluetooth: L2CAP: tolerate zero-ident LE credit indications Kyle Pepper @ 2026-10-13 4:20 ` bluez.test.bot 2026-10-13 14:43 ` [RFC PATCH] " Luiz Augusto von Dentz 2026-10-15 5:28 ` [RFC PATCH v2] " Kyle Pepper 2 siblings, 0 replies; 25+ messages in thread From: bluez.test.bot @ 2026-10-13 4:20 UTC (permalink / raw) To: linux-bluetooth, cochcoder [-- Attachment #1: Type: text/plain, Size: 1073 bytes --] This is automated email and please do not reply to this email! Dear submitter, Thank you for submitting the patches to the linux bluetooth mailing list. This is a CI test results with your patch series: PW Link:https://patchwork.kernel.org/series/1184328/ ---Test result--- Test Summary: CheckPatch PASS 0.52 seconds VerifyFixes PASS 0.08 seconds VerifySignedoff PASS 0.08 seconds GitLint PASS 0.23 seconds SubjectPrefix PASS 0.07 seconds BuildKernel PASS 21.37 seconds CheckAllWarning PASS 25.54 seconds CheckSparse PASS 27.19 seconds BuildKernel32 PASS 22.33 seconds CheckKernelLLVM PASS 24.27 seconds TestRunnerSetup PASS 508.85 seconds TestRunner_l2cap-tester PASS 13.31 seconds IncrementalBuild PASS 20.81 seconds https://github.com/bluez/bluetooth-next/pull/907 --- Regards, Linux Bluetooth ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [RFC PATCH] Bluetooth: L2CAP: tolerate zero-ident LE credit indications 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 ` Luiz Augusto von Dentz 2026-10-15 18:31 ` cochcoder 2026-10-15 5:28 ` [RFC PATCH v2] " Kyle Pepper 2 siblings, 1 reply; 25+ messages in thread From: Luiz Augusto von Dentz @ 2026-10-13 14:43 UTC (permalink / raw) To: Kyle Pepper; +Cc: linux-bluetooth, Marcel Holtmann, linux-kernel Hi Kyle, On Mon, Oct 12, 2026 at 11:20 PM Kyle Pepper <cochcoder@proton.me> wrote: > > A nonconforming ASHA peer sends identifier-zero credit indications > that are discarded, reducing usable transmit credits until audio stalls. > > Tolerate only exact-size credit indications for a connected LE CoC. > Preserve the channel reference, lock and credit-overflow checks and do > not send a response with identifier zero. Other zero-ID commands and > BR/EDR signaling remain unchanged. > > Core Vol 3 Part A section 4 forbids identifier zero. This is an > interoperability RFC requiring maintainer agreement, not a conformance > fix or a claim of automatic stream recovery. > > Assisted-by: OpenCode:gpt-6.1-sol > Signed-off-by: Kyle Pepper <cochcoder@proton.me> > --- > Submission review notes (not part of the commit message): > Core specification forbids identifier zero. This is a narrow opcode > interoperability exception, not a conformance fix. N8 captures contain > zero-ID credit indications. Native malformed/overflow/teardown and > lockdep/KASAN coverage and exception policy remain review gates. > > net/bluetooth/l2cap_core.c | 18 +++++++++++++++++- > 1 file changed, 17 insertions(+), 1 deletion(-) > > diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c > index 49a998804..1a0868437 100644 > --- a/net/bluetooth/l2cap_core.c > +++ b/net/bluetooth/l2cap_core.c > @@ -5328,6 +5328,12 @@ static inline int l2cap_le_credits(struct l2cap_conn *conn, > > l2cap_chan_lock(chan); > > + /* Tolerate identifier-zero credits only for a live LE CoC. */ > + if (!cmd->ident && > + (!credits || chan->state != BT_CONNECTED || > + chan->mode != L2CAP_MODE_LE_FLOWCTL)) > + goto unlock; > + > max_credits = LE_FLOWCTL_MAX_CREDITS - chan->tx_credits; > if (credits > max_credits) { > BT_ERR("LE credits overflow"); > @@ -5923,11 +5929,21 @@ static inline void l2cap_le_sig_channel(struct l2cap_conn *conn, > > BT_DBG("code 0x%2.2x len %d id 0x%2.2x", cmd->code, len, cmd->ident); > > - if (len != skb->len || !cmd->ident) { > + if (len != skb->len) { > BT_DBG("corrupted command"); > goto drop; > } > > + if (!cmd->ident) { > + if (cmd->code != L2CAP_LE_CREDITS || > + len != sizeof(struct l2cap_le_credits)) > + goto drop; > + > + /* Credits have no response or identifier-matched transaction. */ > + l2cap_le_credits(conn, cmd, len, skb->data); > + goto drop; > + } > + > err = l2cap_le_sig_cmd(conn, cmd, len, skb->data); > if (err) { > struct l2cap_cmd_rej_unk rej; > > base-commit: 964430c038967fd06643e866f68115664e1a1bd1 Sashiko flagged a problem with this: https://sashiko.dev/#/patchset/20261013031943.489829-1-cochcoder%40proton.me -- Luiz Augusto von Dentz ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [RFC PATCH] Bluetooth: L2CAP: tolerate zero-ident LE credit indications 2026-10-13 14:43 ` [RFC PATCH] " Luiz Augusto von Dentz @ 2026-10-15 18:31 ` cochcoder 0 siblings, 0 replies; 25+ messages in thread From: cochcoder @ 2026-10-15 18:31 UTC (permalink / raw) To: Luiz Augusto von Dentz; +Cc: linux-bluetooth, Marcel Holtmann, linux-kernel Thanks for flagging this. I've sent v3 as a two-patch series: the security fix first, followed by the dependent zero-ID RFC. Security fix: https://lore.kernel.org/r/20261015181624.48318-1-cochcoder@proton.me Zero-ID RFC: https://lore.kernel.org/r/20261015181624.48318-2-cochcoder@proton.me Thanks, Kyle On Tuesday, October 13th, 2026 at 10:44 AM, Luiz Augusto von Dentz <luiz.dentz@gmail.com> wrote: > Hi Kyle, > > On Mon, Oct 12, 2026 at 11:20 PM Kyle Pepper <cochcoder@proton.me> wrote: > > > > A nonconforming ASHA peer sends identifier-zero credit indications > > that are discarded, reducing usable transmit credits until audio stalls. > > > > Tolerate only exact-size credit indications for a connected LE CoC. > > Preserve the channel reference, lock and credit-overflow checks and do > > not send a response with identifier zero. Other zero-ID commands and > > BR/EDR signaling remain unchanged. > > > > Core Vol 3 Part A section 4 forbids identifier zero. This is an > > interoperability RFC requiring maintainer agreement, not a conformance > > fix or a claim of automatic stream recovery. > > > > Assisted-by: OpenCode:gpt-6.1-sol > > Signed-off-by: Kyle Pepper <cochcoder@proton.me> > > --- > > Submission review notes (not part of the commit message): > > Core specification forbids identifier zero. This is a narrow opcode > > interoperability exception, not a conformance fix. N8 captures contain > > zero-ID credit indications. Native malformed/overflow/teardown and > > lockdep/KASAN coverage and exception policy remain review gates. > > > > net/bluetooth/l2cap_core.c | 18 +++++++++++++++++- > > 1 file changed, 17 insertions(+), 1 deletion(-) > > > > diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c > > index 49a998804..1a0868437 100644 > > --- a/net/bluetooth/l2cap_core.c > > +++ b/net/bluetooth/l2cap_core.c > > @@ -5328,6 +5328,12 @@ static inline int l2cap_le_credits(struct l2cap_conn *conn, > > > > l2cap_chan_lock(chan); > > > > + /* Tolerate identifier-zero credits only for a live LE CoC. */ > > + if (!cmd->ident && > > + (!credits || chan->state != BT_CONNECTED || > > + chan->mode != L2CAP_MODE_LE_FLOWCTL)) > > + goto unlock; > > + > > max_credits = LE_FLOWCTL_MAX_CREDITS - chan->tx_credits; > > if (credits > max_credits) { > > BT_ERR("LE credits overflow"); > > @@ -5923,11 +5929,21 @@ static inline void l2cap_le_sig_channel(struct l2cap_conn *conn, > > > > BT_DBG("code 0x%2.2x len %d id 0x%2.2x", cmd->code, len, cmd->ident); > > > > - if (len != skb->len || !cmd->ident) { > > + if (len != skb->len) { > > BT_DBG("corrupted command"); > > goto drop; > > } > > > > + if (!cmd->ident) { > > + if (cmd->code != L2CAP_LE_CREDITS || > > + len != sizeof(struct l2cap_le_credits)) > > + goto drop; > > + > > + /* Credits have no response or identifier-matched transaction. */ > > + l2cap_le_credits(conn, cmd, len, skb->data); > > + goto drop; > > + } > > + > > err = l2cap_le_sig_cmd(conn, cmd, len, skb->data); > > if (err) { > > struct l2cap_cmd_rej_unk rej; > > > > base-commit: 964430c038967fd06643e866f68115664e1a1bd1 > > Sashiko flagged a problem with this: > > https://sashiko.dev/#/patchset/20261013031943.489829-1-cochcoder%40proton.me > > -- > Luiz Augusto von Dentz > ^ permalink raw reply [flat|nested] 25+ messages in thread
* [RFC PATCH v2] Bluetooth: L2CAP: tolerate zero-ident LE credit indications 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 5:28 ` 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 2 siblings, 2 replies; 25+ messages in thread From: Kyle Pepper @ 2026-10-15 5:28 UTC (permalink / raw) To: linux-bluetooth; +Cc: Marcel Holtmann, Luiz Augusto von Dentz, linux-kernel Tolerate exact-size, nonzero credit grants with identifier zero only for connected LE credit-based channels. Keep the target and pending security checks shared with ordinary identifiers. Do not send a response with identifier zero or relax other signaling commands. This is an interoperability exception to Core Vol 3 Part A section 4, not a conformance fix. Assisted-by: OpenCode:gpt-6.1-sol Signed-off-by: Kyle Pepper <cochcoder@proton.me> --- Changes since v1: - Depend on the separately submitted target/state and pending-security fix. - Require a connected ordinary LE CoC, an exact-size indication and a nonzero grant. Keep fixed/unready targets and ECRED identifier-zero grants excluded. - Preserve shared target/security checks for normal and zero identifiers. - Do not generate any response with identifier zero or relax other commands. REQUIRED security dependency, submitted separately and not resent here: https://lore.kernel.org/linux-bluetooth/20261015052018.129971-1-cochcoder@proton.me/ Signed dependency commit: 61a7ca610078ddf616873e7d4914cb6ac9eae7a3. Apply it first. This mail includes its prerequisite patch-id against upstream 397464709a20. The resulting source tree matches the validated refreshed pair. The Core specification still forbids identifier zero. This remains an interoperability RFC requiring maintainer policy agreement, not a conformance fix. The separate security patch is independently useful without this exception. Fresh current-base controls: all nine focused security cases pass with either security-only or combined kernels. Public credit controls pass 13/14 with security-only (expected positive zero-ID failure), and 14/14 with this revision added. No skips or captured KASAN/lockdep alerts occurred in these focused runs. GCC W=1 changed objects and source-extracted security/target/zero-ID fixtures pass. The refreshed full suite is NOT claimed passing: SCI Fast Mode encounters an invalid response length for HCI opcode 0x20a3 and times out without a complete summary. The same SCI failure reproduces on unpatched current upstream and both revised guests. Initial spaced VM selectors selected broader tests; corrected space-free credit runs are the focused results above. Earlier 134/134 combined results belong to the older reference base, not this fresh full-suite attempt. The combined Arch integration is reported working on hardware, not isolated attribution or exhaustive device/security/teardown coverage. No remote CI pass or severity claim is made. Private security reproducer/logs are not included. Confirm archive rendering of the dependency link before sending. net/bluetooth/l2cap_core.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 7a7f30531..d805b1c62 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -5396,6 +5396,10 @@ static inline int l2cap_le_credits(struct l2cap_conn *conn, chan->mode != L2CAP_MODE_EXT_FLOWCTL)) goto unlock; + /* Tolerate identifier-zero credits only for a live LE CoC. */ + if (!cmd->ident && (!credits || chan->mode != L2CAP_MODE_LE_FLOWCTL)) + goto unlock; + max_credits = LE_FLOWCTL_MAX_CREDITS - chan->tx_credits; if (credits > max_credits) { BT_ERR("LE credits overflow"); @@ -5994,11 +5998,21 @@ static inline void l2cap_le_sig_channel(struct l2cap_conn *conn, BT_DBG("code 0x%2.2x len %d id 0x%2.2x", cmd->code, len, cmd->ident); - if (len != skb->len || !cmd->ident) { + if (len != skb->len) { BT_DBG("corrupted command"); goto drop; } + if (!cmd->ident) { + if (cmd->code != L2CAP_LE_CREDITS || + len != sizeof(struct l2cap_le_credits)) + goto drop; + + /* Credits have no response or identifier-matched transaction. */ + l2cap_le_credits(conn, cmd, len, skb->data); + goto drop; + } + err = l2cap_le_sig_cmd(conn, cmd, len, skb->data); if (err) { struct l2cap_cmd_rej_unk rej; base-commit: 397464709a204da9b8dc3b01d6ea42539d951cd2 prerequisite-patch-id: 658bb8f3b3e38bbbb21f2619434affc641582b7f ^ permalink raw reply related [flat|nested] 25+ messages in thread
* RE: [RFC,v2] Bluetooth: L2CAP: tolerate zero-ident LE credit indications 2026-10-15 5:28 ` [RFC PATCH v2] " Kyle Pepper @ 2026-10-15 6:42 ` bluez.test.bot 2026-10-15 18:16 ` [RFC PATCH v3 1/2] Bluetooth: L2CAP: Keep credits from completing security upgrades Kyle Pepper 1 sibling, 0 replies; 25+ messages in thread From: bluez.test.bot @ 2026-10-15 6:42 UTC (permalink / raw) To: linux-bluetooth, cochcoder [-- Attachment #1: Type: text/plain, Size: 851 bytes --] This is an automated email and please do not reply to this email. Dear Submitter, Thank you for submitting the patches to the linux bluetooth mailing list. While preparing the CI tests, the patches you submitted couldn't be applied to the current HEAD of the repository. ----- Output ----- error: patch failed: net/bluetooth/l2cap_core.c:5396 error: net/bluetooth/l2cap_core.c: patch does not apply hint: Use 'git am --show-current-patch=diff' to see the failed patch hint: When you have resolved this problem, run "git am --continue". hint: If you prefer to skip this patch, run "git am --skip" instead. hint: To restore the original branch and stop patching, run "git am --abort". hint: Disable this message with "git config set advice.mergeConflict false" Please resolve the issue and submit the patches again. --- Regards, Linux Bluetooth ^ permalink raw reply [flat|nested] 25+ messages in thread
* [RFC PATCH v3 1/2] Bluetooth: L2CAP: Keep credits from completing security upgrades 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 ` Kyle Pepper 2026-10-15 18:17 ` [RFC PATCH v3 2/2] Bluetooth: L2CAP: tolerate zero-ident LE credit indications Kyle Pepper ` (3 more replies) 1 sibling, 4 replies; 25+ messages in thread From: Kyle Pepper @ 2026-10-15 18:16 UTC (permalink / raw) To: linux-bluetooth Cc: Marcel Holtmann, Luiz Augusto von Dentz, Johan Hedberg, linux-kernel 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@proton.me> --- 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 ^ permalink raw reply related [flat|nested] 25+ messages in thread
* [RFC PATCH v3 2/2] Bluetooth: L2CAP: tolerate zero-ident LE credit indications 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 ` Kyle Pepper 2026-10-15 20:17 ` [RFC,v3,1/2] Bluetooth: L2CAP: Keep credits from completing security upgrades bluez.test.bot ` (2 subsequent siblings) 3 siblings, 0 replies; 25+ messages in thread From: Kyle Pepper @ 2026-10-15 18:17 UTC (permalink / raw) To: linux-bluetooth Cc: Marcel Holtmann, Luiz Augusto von Dentz, Johan Hedberg, linux-kernel Tolerate exact-size, nonzero credit grants with identifier zero only for connected LE credit-based channels. Keep the target and pending security checks shared with ordinary identifiers. Do not send a response with identifier zero or relax other signaling commands. This is an interoperability exception to Core Vol 3 Part A section 4, not a conformance fix. Assisted-by: OpenCode:gpt-6.1-sol Signed-off-by: Kyle Pepper <cochcoder@proton.me> --- Changes since v2: - Include the unchanged security prerequisite as patch 1/2 in this series, rather than relying on CI to apply a separately submitted dependency. - Refresh onto bluetooth-next ff8b09806b7b without a source delta. - Wrap test notes to keep all prose within GitLint's 80-column limit. The saved v2 report failed application at l2cap_core.c:5396. That patch depends on the security fix, which was not yet upstream. A dependency link and prerequisite-patch-id did not make that CI run apply it first. Apply this entire series in order; patch 2 alone is not a valid input. The Core specification still forbids identifier zero. This remains an interoperability RFC requiring maintainer policy agreement, not a conformance fix. Patch 1 is independently useful without this exception. Earlier 397464709a20 runs passed all nine focused security controls with security-only and combined guests. Public credit controls passed 13/14 security-only (expected positive zero-ID failure), 14/14 combined, with no skips or captured kernel alerts. Source-extracted fixtures and GCC W=1 changed objects passed. These are prior runs, not fresh v3 VMs. The earlier full-suite attempt was incomplete due to an SCI timeout also seen on the unpatched base. No fresh full-suite or remote CI pass is claimed for v3. The combined Arch trial is reported working on hardware, not isolated attribution or exhaustive device/security/teardown coverage. Private security reproducer/logs and the archived BlueZ test companion are excluded. This series contains only the two kernel patches. net/bluetooth/l2cap_core.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 7a7f30531..d805b1c62 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -5396,6 +5396,10 @@ static inline int l2cap_le_credits(struct l2cap_conn *conn, chan->mode != L2CAP_MODE_EXT_FLOWCTL)) goto unlock; + /* Tolerate identifier-zero credits only for a live LE CoC. */ + if (!cmd->ident && (!credits || chan->mode != L2CAP_MODE_LE_FLOWCTL)) + goto unlock; + max_credits = LE_FLOWCTL_MAX_CREDITS - chan->tx_credits; if (credits > max_credits) { BT_ERR("LE credits overflow"); @@ -5994,11 +5998,21 @@ static inline void l2cap_le_sig_channel(struct l2cap_conn *conn, BT_DBG("code 0x%2.2x len %d id 0x%2.2x", cmd->code, len, cmd->ident); - if (len != skb->len || !cmd->ident) { + if (len != skb->len) { BT_DBG("corrupted command"); goto drop; } + if (!cmd->ident) { + if (cmd->code != L2CAP_LE_CREDITS || + len != sizeof(struct l2cap_le_credits)) + goto drop; + + /* Credits have no response or identifier-matched transaction. */ + l2cap_le_credits(conn, cmd, len, skb->data); + goto drop; + } + err = l2cap_le_sig_cmd(conn, cmd, len, skb->data); if (err) { struct l2cap_cmd_rej_unk rej; ^ permalink raw reply related [flat|nested] 25+ messages in thread
* RE: [RFC,v3,1/2] Bluetooth: L2CAP: Keep credits from completing security upgrades 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 ` bluez.test.bot 2026-10-31 18:09 ` [RFC PATCH v3 1/2] " cochcoder 2026-11-06 2:17 ` [RFC PATCH v4 " Kyle Pepper 3 siblings, 0 replies; 25+ messages in thread From: bluez.test.bot @ 2026-10-15 20:17 UTC (permalink / raw) To: linux-bluetooth, cochcoder [-- Attachment #1: Type: text/plain, Size: 1073 bytes --] This is automated email and please do not reply to this email! Dear submitter, Thank you for submitting the patches to the linux bluetooth mailing list. This is a CI test results with your patch series: PW Link:https://patchwork.kernel.org/series/1186712/ ---Test result--- Test Summary: CheckPatch PASS 1.74 seconds VerifyFixes PASS 0.12 seconds VerifySignedoff PASS 0.12 seconds GitLint PASS 0.64 seconds SubjectPrefix PASS 0.23 seconds BuildKernel PASS 31.51 seconds CheckAllWarning PASS 33.73 seconds CheckSparse PASS 36.47 seconds BuildKernel32 PASS 29.38 seconds CheckKernelLLVM PASS 35.28 seconds TestRunnerSetup PASS 702.28 seconds TestRunner_l2cap-tester PASS 12.79 seconds IncrementalBuild PASS 31.19 seconds https://github.com/bluez/bluetooth-next/pull/952 --- Regards, Linux Bluetooth ^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [RFC PATCH v3 1/2] Bluetooth: L2CAP: Keep credits from completing security upgrades 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 ` cochcoder 2026-11-06 2:17 ` [RFC PATCH v4 " Kyle Pepper 3 siblings, 0 replies; 25+ messages in thread From: cochcoder @ 2026-10-31 18:09 UTC (permalink / raw) To: linux-bluetooth Cc: Marcel Holtmann, Luiz Augusto von Dentz, Johan Hedberg, linux-kernel 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@proton.me> 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@proton.me> > --- > 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 > ^ permalink raw reply [flat|nested] 25+ messages in thread
* [RFC PATCH v4 1/2] Bluetooth: L2CAP: Keep credits from completing security upgrades 2026-10-15 18:16 ` [RFC PATCH v3 1/2] Bluetooth: L2CAP: Keep credits from completing security upgrades Kyle Pepper ` (2 preceding siblings ...) 2026-10-31 18:09 ` [RFC PATCH v3 1/2] " cochcoder @ 2026-11-06 2:17 ` Kyle Pepper 2026-11-06 2:18 ` [RFC PATCH v4 2/2] Bluetooth: L2CAP: tolerate zero-ident LE credit indications Kyle Pepper ` (2 more replies) 3 siblings, 3 replies; 25+ messages in thread From: Kyle Pepper @ 2026-11-06 2:17 UTC (permalink / raw) To: linux-bluetooth; +Cc: luiz.dentz, marcel Validate credit targets and retain credits without sending or resuming while an LE or ECRED security upgrade is pending. Keep the requested security level until encryption and key size satisfy it. Schedule immediate channel timeout work if the upgrade fails. This runs the normal close path outside the security callback list traversal and keeps traffic suspended until teardown. Complete deferred security without accepting the connection or sending queued data. Drain connected channels after successful security completion. Resume LE credit-based sockets only when credits remain, including background encryption changes with no locally pending upgrade. Fixes: fad5fc89594d ("Bluetooth: Add LE flow control discipline") Assisted-by: LLM Signed-off-by: Kyle Pepper <cochcoder@proton.me> --- v4: Fix failed/deferred security completion and credit-aware wakeups. AI review: https://sashiko.dev/#/patchset/20261015181624.48318-1-cochcoder%40proton.me net/bluetooth/l2cap_core.c | 50 ++++++++++++++++++++++++++++++++++++-- net/bluetooth/l2cap_sock.c | 9 +++++-- 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 7dcb0586a..9463ac215 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -5380,6 +5380,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 +5398,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); @@ -7976,6 +7984,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 +7993,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 +8007,39 @@ static void l2cap_security_cfm(struct hci_conn *hcon, u8 status, u8 encrypt) continue; } + 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)) { + /* Keep traffic suspended until timeout work closes + * the channel outside this list traversal. + */ + __set_chan_timer(chan, 0); + l2cap_chan_unlock(chan); + continue; + } + + clear_bit(FLAG_PENDING_SECURITY, &chan->flags); + chan->sec_level = hcon->sec_level; + + /* Security completion must not accept deferred setup. */ + if (chan->state == BT_CONNECT2) { + if (chan->tx_credits) + chan->ops->resume(chan); + l2cap_chan_unlock(chan); + continue; + } + + l2cap_le_flowctl_send(chan); + } + if (!status && (chan->state == BT_CONNECTED || chan->state == BT_CONFIG)) { - chan->ops->resume(chan); + if (!le_flowctl || 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 7a2551ef7..2cf91d0a0 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: a063a00e6cd9e354aa6d67a154a0527951d7f881 ^ permalink raw reply related [flat|nested] 25+ messages in thread
* [RFC PATCH v4 2/2] Bluetooth: L2CAP: tolerate zero-ident LE credit indications 2026-11-06 2:17 ` [RFC PATCH v4 " Kyle Pepper @ 2026-11-06 2:18 ` 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 2 siblings, 0 replies; 25+ messages in thread From: Kyle Pepper @ 2026-11-06 2:18 UTC (permalink / raw) To: linux-bluetooth; +Cc: luiz.dentz, marcel Keep the narrow identifier-zero exception for nonzero credit grants to connected LE CoC channels. Apply the same mode, state and pending security checks as ordinary credit indications. This interoperability exception depends on the security fix and still requires maintainer policy approval. Assisted-by: LLM Signed-off-by: Kyle Pepper <cochcoder@proton.me> --- No code change; depends on patch 1. Maintainer policy approval remains required. AI review: https://sashiko.dev/#/patchset/20261015181624.48318-1-cochcoder%40proton.me net/bluetooth/l2cap_core.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 9463ac215..4bef75235 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -5385,6 +5385,10 @@ static inline int l2cap_le_credits(struct l2cap_conn *conn, chan->mode != L2CAP_MODE_EXT_FLOWCTL)) goto unlock; + /* Tolerate identifier-zero credits only for a live LE CoC. */ + if (!cmd->ident && (!credits || chan->mode != L2CAP_MODE_LE_FLOWCTL)) + goto unlock; + max_credits = LE_FLOWCTL_MAX_CREDITS - chan->tx_credits; if (credits > max_credits) { BT_ERR("LE credits overflow"); @@ -5994,11 +5998,21 @@ static inline void l2cap_le_sig_channel(struct l2cap_conn *conn, BT_DBG("code 0x%2.2x len %d id 0x%2.2x", cmd->code, len, cmd->ident); - if (len != skb->len || !cmd->ident) { + if (len != skb->len) { BT_DBG("corrupted command"); goto drop; } + if (!cmd->ident) { + if (cmd->code != L2CAP_LE_CREDITS || + len != sizeof(struct l2cap_le_credits)) + goto drop; + + /* Credits have no response or identifier-matched transaction. */ + l2cap_le_credits(conn, cmd, len, skb->data); + goto drop; + } + err = l2cap_le_sig_cmd(conn, cmd, len, skb->data); if (err) { struct l2cap_cmd_rej_unk rej; ^ permalink raw reply related [flat|nested] 25+ messages in thread
* RE: [RFC,v4,1/2] Bluetooth: L2CAP: Keep credits from completing security upgrades 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 ` bluez.test.bot 2026-11-06 4:55 ` [RFC PATCH v5 1/2] " Kyle Pepper 2 siblings, 0 replies; 25+ messages in thread From: bluez.test.bot @ 2026-11-06 3:17 UTC (permalink / raw) To: linux-bluetooth, cochcoder [-- Attachment #1: Type: text/plain, Size: 4202 bytes --] This is automated email and please do not reply to this email! Dear submitter, Thank you for submitting the patches to the linux bluetooth mailing list. This is a CI test results with your patch series: PW Link:https://patchwork.kernel.org/series/1203684/ ---Test result--- Test Summary: CheckPatch FAIL 1.20 seconds VerifyFixes PASS 0.12 seconds VerifySignedoff PASS 0.07 seconds GitLint PASS 0.45 seconds SubjectPrefix PASS 0.12 seconds BuildKernel PASS 24.36 seconds CheckAllWarning PASS 32.27 seconds CheckSparse PASS 30.88 seconds BuildKernel32 PASS 24.22 seconds CheckKernelLLVM PASS 25.68 seconds TestRunnerSetup PASS 558.75 seconds TestRunner_l2cap-tester FAIL 11.79 seconds IncrementalBuild PASS 28.69 seconds Details ############################## Test: CheckPatch - FAIL Desc: Run checkpatch.pl script Output: [RFC,v4,1/2] Bluetooth: L2CAP: Keep credits from completing security upgrades WARNING: Non-standard signature: Assisted-by: #99: Assisted-by: LLM ERROR: Unrecognized email address: 'LLM' #99: Assisted-by: LLM total: 1 errors, 1 warnings, 0 checks, 99 lines checked NOTE: For some of the reported defects, checkpatch may be able to mechanically convert to the typical style using --fix or --fix-inplace. /home/runner/work/bluetooth-next/bluetooth-next/src/patch/14955882.patch has style problems, please review. NOTE: Ignored message types: UNKNOWN_COMMIT_ID NOTE: If any of the errors are false positives, please report them to the maintainer, see CHECKPATCH in MAINTAINERS. [RFC,v4,2/2] Bluetooth: L2CAP: tolerate zero-ident LE credit indications WARNING: Non-standard signature: Assisted-by: #93: Assisted-by: LLM ERROR: Unrecognized email address: 'LLM' #93: Assisted-by: LLM total: 1 errors, 1 warnings, 0 checks, 32 lines checked NOTE: For some of the reported defects, checkpatch may be able to mechanically convert to the typical style using --fix or --fix-inplace. /home/runner/work/bluetooth-next/bluetooth-next/src/patch/14955883.patch has style problems, please review. NOTE: Ignored message types: UNKNOWN_COMMIT_ID NOTE: If any of the errors are false positives, please report them to the maintainer, see CHECKPATCH in MAINTAINERS. ############################## Test: TestRunner_l2cap-tester - FAIL Desc: Run l2cap-tester with test-runner Output: Sanitizer errors: ==35==ERROR: AddressSanitizer: heap-use-after-free on address 0x7b8639ae33d0 at pc 0x5583cbda5eee bp 0x7ffd73a0c830 sp 0x7ffd73a0c820 READ of size 4 at 0x7b8639ae33d0 thread T0 #0 0x5583cbda5eed in timeout_callback src/shared/timeout-mainloop.c:33 #1 0x5583cbda6ed5 in timeout_callback src/shared/mainloop.c:271 #2 0x5583cbda8006 in mainloop_run src/shared/mainloop.c:125 #3 0x5583cbdaa96a in mainloop_run_with_signal src/shared/mainloop-notify.c:196 #4 0x5583cbdafc4b in tester_run src/shared/tester.c:1297 #5 0x5583cbcd91dd in main tools/l2cap-tester.c:3918 #6 0x7f463aa34600 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:59 #7 0x7f463aa34717 in __libc_start_main_impl ../csu/libc-start.c:360 #8 0x5583cbcdfce4 in _start (/home/runner/work/bluetooth-next/bluetooth-next/src/bluez/tools/l2cap-tester+0x16dce4) (BuildId: 0d2067a7ef8fe61fcf622598953c7d0f996221a0) 0x7b8639ae33d0 is located 0 bytes inside of 40-byte region [0x7b8639ae33d0,0x7b8639ae33f8) freed by thread T0 here: #0 0x7f463b3723ff in free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:51 #1 0x5583cbda63ca in timeout_destroy src/shared/mainloop.c:252 previously allocated by thread T0 here: #0 0x7f463b37360f in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:67 #1 0x5583cbd90335 in util_malloc src/shared/util.c:46 SUMMARY: AddressSanitizer: heap-use-after-free src/shared/timeout-mainloop.c:33 in timeout_callback No test result found https://github.com/bluez/bluetooth-next/pull/1075 --- Regards, Linux Bluetooth ^ permalink raw reply [flat|nested] 25+ messages in thread
* [RFC PATCH v5 1/2] Bluetooth: L2CAP: Keep credits from completing security upgrades 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 ` Kyle Pepper 2026-11-06 4:56 ` [RFC PATCH v5 2/2] Bluetooth: L2CAP: tolerate zero-ident LE credit indications Kyle Pepper ` (2 more replies) 2 siblings, 3 replies; 25+ messages in thread From: Kyle Pepper @ 2026-11-06 4:55 UTC (permalink / raw) To: linux-bluetooth; +Cc: luiz.dentz, marcel Retain LE and ECRED credits without sending or resuming sockets while a security upgrade is pending. Check the shared send path so a send that crossed socket readiness or allocation lock gaps cannot drain queued data at the old security level. Reject deferred acceptance with EAGAIN while security is pending. Keep the requested level unchanged and return EBUSY for another BT_SECURITY request until completion or teardown, including the interval before failed-upgrade timeout work closes the channel. Validate encryption and key size before completing security, preserve deferred setup, and resume connected sockets only when credits remain. Schedule failed-upgrade teardown outside the channel-list traversal. Assisted-by: LLM Signed-off-by: Kyle Pepper <cochcoder@proton.me> --- Changes in v5: - Guard the shared send path across socket and allocation lock gaps. - Keep deferred acceptance blocked while security is pending. - Reject another BT_SECURITY request until completion or teardown. AI review: https://sashiko.dev/#/patchset/20261106021723.631339-1-cochcoder%40proton.me GCC/Clang sanitizer fixtures, negative controls and W=1 object builds pass. The local full kernel image builds with KASAN and lockdep enabled. Native L2CAP and hardware behavior remain unverified. PR1075's BlueZ timeout use-after-free and checkpatch attribution compatibility need separate CI fixes. AI assistance: Codex prepared source revisions, descriptions and local tests in response to the v4 automated review. Attribution is retained. net/bluetooth/l2cap_core.c | 53 ++++++++++++++++++++++++++++++++++++-- net/bluetooth/l2cap_sock.c | 22 ++++++++++++++-- 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 7dcb0586a..ae6659fab 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -2647,6 +2647,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--; @@ -5380,6 +5383,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 +5401,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); @@ -7976,6 +7987,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 +7996,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 +8010,39 @@ static void l2cap_security_cfm(struct hci_conn *hcon, u8 status, u8 encrypt) continue; } + 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)) { + /* Keep traffic suspended until timeout work closes + * the channel outside this list traversal. + */ + __set_chan_timer(chan, 0); + l2cap_chan_unlock(chan); + continue; + } + + clear_bit(FLAG_PENDING_SECURITY, &chan->flags); + chan->sec_level = hcon->sec_level; + + /* Security completion must not accept deferred setup. */ + if (chan->state == BT_CONNECT2) { + if (chan->tx_credits) + chan->ops->resume(chan); + l2cap_chan_unlock(chan); + continue; + } + + l2cap_le_flowctl_send(chan); + } + if (!status && (chan->state == BT_CONNECTED || chan->state == BT_CONFIG)) { - chan->ops->resume(chan); + if (!le_flowctl || 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 7a2551ef7..cc00ef688 100644 --- a/net/bluetooth/l2cap_sock.c +++ b/net/bluetooth/l2cap_sock.c @@ -972,6 +972,14 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname, break; } + /* Preserve an outstanding LE credit-based security request. */ + if ((chan->mode == L2CAP_MODE_LE_FLOWCTL || + chan->mode == L2CAP_MODE_EXT_FLOWCTL) && + test_bit(FLAG_PENDING_SECURITY, &chan->flags)) { + err = -EBUSY; + break; + } + chan->sec_level = sec.level; conn = l2cap_chan_conn(chan); @@ -993,10 +1001,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; } @@ -1263,6 +1276,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; base-commit: a063a00e6cd9e354aa6d67a154a0527951d7f881 ^ permalink raw reply related [flat|nested] 25+ messages in thread
* [RFC PATCH v5 2/2] Bluetooth: L2CAP: tolerate zero-ident LE credit indications 2026-11-06 4:55 ` [RFC PATCH v5 1/2] " Kyle Pepper @ 2026-11-06 4:56 ` 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 2 siblings, 1 reply; 25+ messages in thread From: Kyle Pepper @ 2026-11-06 4:56 UTC (permalink / raw) To: linux-bluetooth; +Cc: luiz.dentz, marcel Cochlear Nucleus N8 captures contain LE credit indications with identifier zero. The specification forbids zero identifiers, so the current parser discards these grants and leaves the channel without usable transmit credits. Accept nonzero grants only for connected legacy LE CoC channels. Keep ECRED and all other commands excluded because the observed interoperability issue concerns legacy LE CoC. Apply normal credit overflow and pending-security handling, including the shared transmit guard from the preceding patch. Let the credit handler validate the exact payload size after the signaling parser validates framing. Assisted-by: LLM Signed-off-by: Kyle Pepper <cochcoder@proton.me> --- Changes in v5: - Explain the observed Nucleus N8 behavior and legacy LE CoC restriction. - Use the credit handler's existing exact payload-size validation. - Depend on v5's shared security transmit guard. AI review: https://sashiko.dev/#/patchset/20261106021723.631339-1-cochcoder%40proton.me Credit/parser fixtures pass. ECRED remains excluded from the zero-ID exception. Native L2CAP and hardware behavior remain unverified. The interoperability exception remains subject to maintainer policy approval. AI assistance: Codex prepared source revisions, descriptions and local tests in response to the v4 automated review. Attribution is retained. net/bluetooth/l2cap_core.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index ae6659fab..d3b875c47 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -5388,6 +5388,10 @@ static inline int l2cap_le_credits(struct l2cap_conn *conn, chan->mode != L2CAP_MODE_EXT_FLOWCTL)) goto unlock; + /* Tolerate identifier-zero credits only for a live LE CoC. */ + if (!cmd->ident && (!credits || chan->mode != L2CAP_MODE_LE_FLOWCTL)) + goto unlock; + max_credits = LE_FLOWCTL_MAX_CREDITS - chan->tx_credits; if (credits > max_credits) { BT_ERR("LE credits overflow"); @@ -5997,11 +6001,20 @@ static inline void l2cap_le_sig_channel(struct l2cap_conn *conn, BT_DBG("code 0x%2.2x len %d id 0x%2.2x", cmd->code, len, cmd->ident); - if (len != skb->len || !cmd->ident) { + if (len != skb->len) { BT_DBG("corrupted command"); goto drop; } + if (!cmd->ident) { + if (cmd->code != L2CAP_LE_CREDITS) + goto drop; + + /* Credits have no response or identifier-matched transaction. */ + l2cap_le_credits(conn, cmd, len, skb->data); + goto drop; + } + err = l2cap_le_sig_cmd(conn, cmd, len, skb->data); if (err) { struct l2cap_cmd_rej_unk rej; ^ permalink raw reply related [flat|nested] 25+ messages in thread
* Re: [RFC PATCH v5 2/2] Bluetooth: L2CAP: tolerate zero-ident LE credit indications 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 0 siblings, 0 replies; 25+ messages in thread From: Luiz Augusto von Dentz @ 2026-11-06 16:30 UTC (permalink / raw) To: Kyle Pepper; +Cc: linux-bluetooth, marcel Hi Kyle, On Thu, Nov 5, 2026 at 11:56 PM Kyle Pepper <cochcoder@proton.me> wrote: > > Cochlear Nucleus N8 captures contain LE credit indications with > identifier zero. The specification forbids zero identifiers, so the > current parser discards these grants and leaves the channel without > usable transmit credits. > > Accept nonzero grants only for connected legacy LE CoC channels. Keep > ECRED and all other commands excluded because the observed > interoperability issue concerns legacy LE CoC. Apply normal credit > overflow and pending-security handling, including the shared transmit > guard from the preceding patch. > > Let the credit handler validate the exact payload size after the > signaling parser validates framing. > > Assisted-by: LLM > > Signed-off-by: Kyle Pepper <cochcoder@proton.me> > --- > Changes in v5: > - Explain the observed Nucleus N8 behavior and legacy LE CoC restriction. > - Use the credit handler's existing exact payload-size validation. > - Depend on v5's shared security transmit guard. > > AI review: > https://sashiko.dev/#/patchset/20261106021723.631339-1-cochcoder%40proton.me > > Credit/parser fixtures pass. ECRED remains excluded from the zero-ID exception. > Native L2CAP and hardware behavior remain unverified. The interoperability > exception remains subject to maintainer policy approval. > > AI assistance: Codex prepared source revisions, descriptions and local tests > in response to the v4 automated review. Attribution is retained. > > net/bluetooth/l2cap_core.c | 15 ++++++++++++++- > 1 file changed, 14 insertions(+), 1 deletion(-) > > diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c > index ae6659fab..d3b875c47 100644 > --- a/net/bluetooth/l2cap_core.c > +++ b/net/bluetooth/l2cap_core.c > @@ -5388,6 +5388,10 @@ static inline int l2cap_le_credits(struct l2cap_conn *conn, > chan->mode != L2CAP_MODE_EXT_FLOWCTL)) > goto unlock; > > + /* Tolerate identifier-zero credits only for a live LE CoC. */ > + if (!cmd->ident && (!credits || chan->mode != L2CAP_MODE_LE_FLOWCTL)) > + goto unlock; > + > max_credits = LE_FLOWCTL_MAX_CREDITS - chan->tx_credits; > if (credits > max_credits) { > BT_ERR("LE credits overflow"); > @@ -5997,11 +6001,20 @@ static inline void l2cap_le_sig_channel(struct l2cap_conn *conn, > > BT_DBG("code 0x%2.2x len %d id 0x%2.2x", cmd->code, len, cmd->ident); > > - if (len != skb->len || !cmd->ident) { > + if (len != skb->len) { > BT_DBG("corrupted command"); > goto drop; > } > > + if (!cmd->ident) { > + if (cmd->code != L2CAP_LE_CREDITS) > + goto drop; > + > + /* Credits have no response or identifier-matched transaction. */ > + l2cap_le_credits(conn, cmd, len, skb->data); > + goto drop; > + } > + > err = l2cap_le_sig_cmd(conn, cmd, len, skb->data); > if (err) { > struct l2cap_cmd_rej_unk rej; > Sashiko is flagging quite a few problems with these changes: https://sashiko.dev/#/patchset/20261106021723.631339-1-cochcoder%40proton.me -- Luiz Augusto von Dentz ^ permalink raw reply [flat|nested] 25+ messages in thread
* RE: [RFC,v5,1/2] Bluetooth: L2CAP: Keep credits from completing security upgrades 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 10:40 ` bluez.test.bot 2026-11-06 17:40 ` [RFC PATCH v6 1/2] " Kyle Pepper 2 siblings, 0 replies; 25+ messages in thread From: bluez.test.bot @ 2026-11-06 10:40 UTC (permalink / raw) To: linux-bluetooth, cochcoder [-- Attachment #1: Type: text/plain, Size: 4205 bytes --] This is automated email and please do not reply to this email! Dear submitter, Thank you for submitting the patches to the linux bluetooth mailing list. This is a CI test results with your patch series: PW Link:https://patchwork.kernel.org/series/1203754/ ---Test result--- Test Summary: CheckPatch FAIL 1.57 seconds VerifyFixes PASS 0.09 seconds VerifySignedoff PASS 0.09 seconds GitLint PASS 0.54 seconds SubjectPrefix PASS 0.16 seconds BuildKernel PASS 32.32 seconds CheckAllWarning PASS 36.37 seconds CheckSparse PASS 39.18 seconds BuildKernel32 PASS 31.46 seconds CheckKernelLLVM PASS 35.59 seconds TestRunnerSetup PASS 751.80 seconds TestRunner_l2cap-tester FAIL 10.71 seconds IncrementalBuild PASS 32.56 seconds Details ############################## Test: CheckPatch - FAIL Desc: Run checkpatch.pl script Output: [RFC,v5,1/2] Bluetooth: L2CAP: Keep credits from completing security upgrades WARNING: Non-standard signature: Assisted-by: #94: Assisted-by: LLM ERROR: Unrecognized email address: 'LLM' #94: Assisted-by: LLM total: 1 errors, 1 warnings, 0 checks, 133 lines checked NOTE: For some of the reported defects, checkpatch may be able to mechanically convert to the typical style using --fix or --fix-inplace. /home/runner/work/bluetooth-next/bluetooth-next/src/patch/14956133.patch has style problems, please review. NOTE: Ignored message types: UNKNOWN_COMMIT_ID NOTE: If any of the errors are false positives, please report them to the maintainer, see CHECKPATCH in MAINTAINERS. [RFC,v5,2/2] Bluetooth: L2CAP: tolerate zero-ident LE credit indications WARNING: Non-standard signature: Assisted-by: #101: Assisted-by: LLM ERROR: Unrecognized email address: 'LLM' #101: Assisted-by: LLM total: 1 errors, 1 warnings, 0 checks, 31 lines checked NOTE: For some of the reported defects, checkpatch may be able to mechanically convert to the typical style using --fix or --fix-inplace. /home/runner/work/bluetooth-next/bluetooth-next/src/patch/14956134.patch has style problems, please review. NOTE: Ignored message types: UNKNOWN_COMMIT_ID NOTE: If any of the errors are false positives, please report them to the maintainer, see CHECKPATCH in MAINTAINERS. ############################## Test: TestRunner_l2cap-tester - FAIL Desc: Run l2cap-tester with test-runner Output: Sanitizer errors: ==37==ERROR: AddressSanitizer: heap-use-after-free on address 0x7bea177e33d0 at pc 0x5565a9c25eee bp 0x7ffee26491d0 sp 0x7ffee26491c0 READ of size 4 at 0x7bea177e33d0 thread T0 #0 0x5565a9c25eed in timeout_callback src/shared/timeout-mainloop.c:33 #1 0x5565a9c26ed5 in timeout_callback src/shared/mainloop.c:271 #2 0x5565a9c28006 in mainloop_run src/shared/mainloop.c:125 #3 0x5565a9c2a96a in mainloop_run_with_signal src/shared/mainloop-notify.c:196 #4 0x5565a9c2fc4b in tester_run src/shared/tester.c:1297 #5 0x5565a9b591dd in main tools/l2cap-tester.c:3918 #6 0x7faa18726600 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:59 #7 0x7faa18726717 in __libc_start_main_impl ../csu/libc-start.c:360 #8 0x5565a9b5fce4 in _start (/home/runner/work/bluetooth-next/bluetooth-next/src/bluez/tools/l2cap-tester+0x16dce4) (BuildId: 0d2067a7ef8fe61fcf622598953c7d0f996221a0) 0x7bea177e33d0 is located 0 bytes inside of 40-byte region [0x7bea177e33d0,0x7bea177e33f8) freed by thread T0 here: #0 0x7faa190643ff in free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:51 #1 0x5565a9c263ca in timeout_destroy src/shared/mainloop.c:252 previously allocated by thread T0 here: #0 0x7faa1906560f in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:67 #1 0x5565a9c10335 in util_malloc src/shared/util.c:46 SUMMARY: AddressSanitizer: heap-use-after-free src/shared/timeout-mainloop.c:33 in timeout_callback No test result found https://github.com/bluez/bluetooth-next/pull/1077 --- Regards, Linux Bluetooth ^ permalink raw reply [flat|nested] 25+ messages in thread
* [RFC PATCH v6 1/2] Bluetooth: L2CAP: Keep credits from completing security upgrades 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 10:40 ` [RFC,v5,1/2] Bluetooth: L2CAP: Keep credits from completing security upgrades bluez.test.bot @ 2026-11-06 17:40 ` Kyle Pepper 2026-11-06 17:40 ` [RFC PATCH v6 2/2] Bluetooth: L2CAP: tolerate zero-ident LE credit indications Kyle Pepper ` (2 more replies) 2 siblings, 3 replies; 25+ messages in thread From: Kyle Pepper @ 2026-11-06 17:40 UTC (permalink / raw) To: linux-bluetooth; +Cc: luiz.dentz, marcel 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 until completion or teardown. Finish pending upgrades from connection work so failed channels and ECRED batches can be removed outside the security callback's list walk. Keep a failed upgrade terminal if another confirmation arrives before the work runs. Report local SMP failure through the same work without calling security callbacks while the SMP channel is locked. Wake deferred sockets after successful security even with no initial credits. Check every sibling's security before an ECRED batch response, let refusal override pending acceptance, and retry the response after security completes. Serialize BT_SECURITY with the batch response. Wake failed senders, preserve disconnect errors through teardown, and clear security and suspension state when removing the channel. Assisted-by: LLM Signed-off-by: Kyle Pepper <cochcoder@proton.me> --- Changes in v6: - Finish pending upgrades from referenced connection work. - Keep failure terminal across later security confirmations. - Report local SMP failure without taking channel-list locks from SMP. - Wake deferred sockets even with zero initial credits. - Wake failed senders and preserve errors through disconnect teardown. - Clear pending-security and suspension state during teardown. - Serialize BT_SECURITY with ECRED batch readiness and response handling. - Let ECRED refusal override pending acceptance and retry after security. AI review of v5: https://sashiko.dev/#/patchset/20261106045515.853897-1-cochcoder%40proton.me GCC/Clang ASan/UBSan fixtures cover the seven reported paths and successful upgrades, with v5 baseline and isolated mutation controls. GCC/Clang and ECRED-disabled W=1 objects and a KASAN/lockdep kernel image pass locally. Native L2CAP/concurrency and hardware behavior remain unverified. The userspace timeout sanitizer failure and the checker's handling of required AI attribution remain separate CI infrastructure issues. A remote CI pass is not claimed. AI assistance: Codex prepared this revision, descriptions and local tests. Attribution is retained. include/net/bluetooth/l2cap.h | 5 ++ net/bluetooth/l2cap_core.c | 161 +++++++++++++++++++++++++++++++++- net/bluetooth/l2cap_sock.c | 134 +++++++++++++++++----------- net/bluetooth/smp.c | 1 + 4 files changed, 244 insertions(+), 57 deletions(-) diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h index 2a05bda8a..f09c2696f 100644 --- a/include/net/bluetooth/l2cap.h +++ b/include/net/bluetooth/l2cap.h @@ -636,6 +636,8 @@ struct l2cap_conn { struct sk_buff_head pending_rx; struct work_struct pending_rx_work; + struct work_struct security_work; + atomic_t security_failed; struct delayed_work id_addr_timer; @@ -758,6 +760,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 +987,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..24360d8e1 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); @@ -704,6 +705,9 @@ 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); + clear_bit(FLAG_SECURITY_FAILED, &chan->flags); BT_DBG("chan %p, err %d, state %s", chan, err, state_to_string(chan->state)); @@ -2647,6 +2651,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 +3986,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 +4000,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 */ @@ -4079,7 +4093,9 @@ static void l2cap_ecred_rsp_defer(struct l2cap_chan *chan, void *data) if (!rsp->pdu.rsp.result) rsp_flex->dcid[rsp->count++] = cpu_to_le16(chan->scid); else - l2cap_chan_del(chan, ECONNRESET); + l2cap_chan_del(chan, rsp->pdu.rsp.result == + cpu_to_le16(L2CAP_CR_LE_AUTHORIZATION) ? + ECONNREFUSED : ECONNRESET); done: if (locked) @@ -5380,6 +5396,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 +5414,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 +7534,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 +7988,109 @@ 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); + if (!queue_work(conn->hcon->hdev->workqueue, &conn->security_work)) + l2cap_conn_put(conn); +} + +void l2cap_security_failed(struct l2cap_conn *conn) +{ + if (conn->hcon->type != LE_LINK) + return; + + atomic_set(&conn->security_failed, 1); + 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; + + hci_dev_lock(hcon->hdev); + if (hcon->l2cap_data != conn) + goto done; + + mutex_lock(&conn->lock); + + if (atomic_xchg(&conn->security_failed, 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); + } + } + + 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 +8105,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 +8114,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 +8128,25 @@ static void l2cap_security_cfm(struct hci_conn *hcon, u8 status, u8 encrypt) continue; } + 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); 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 7a2551ef7..3f4ab80b3 100644 --- a/net/bluetooth/l2cap_sock.c +++ b/net/bluetooth/l2cap_sock.c @@ -927,12 +927,70 @@ 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) || + (conn && atomic_read(&conn->security_failed)))) + return -EBUSY; + + chan->sec_level = sec.level; + if (!conn) + return 0; + + lockdep_assert_held(&conn->lock); + + 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 +1006,20 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname, if (level != SOL_BLUETOOTH) return -ENOPROTOOPT; + if (optname == BT_SECURITY) { + bool have_conn = l2cap_chan_lock_conn(chan); + + lock_sock(sk); + err = l2cap_sock_set_security(sk, chan, optval, optlen); + release_sock(sk); + l2cap_chan_unlock_conn(chan, have_conn); + return err; + } + 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; - - conn = l2cap_chan_conn(chan); - if (!conn) - break; - - /* change security for LE channels */ - if (chan->scid == L2CAP_CID_ATT) { - if (smp_conn_security(conn->hcon, sec.level)) { - err = -EINVAL; - break; - } - - set_bit(FLAG_PENDING_SECURITY, &chan->flags); - sk->sk_state = BT_CONFIG; - chan->state = BT_CONFIG; - - /* 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; - case BT_DEFER_SETUP: if (sk->sk_state != BT_BOUND && sk->sk_state != BT_LISTEN) { err = -EINVAL; @@ -1263,6 +1281,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; @@ -1736,6 +1759,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 +1776,8 @@ 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 (err) + sk->sk_err = err; if (parent) { /* @@ -1788,8 +1813,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 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); base-commit: a063a00e6cd9e354aa6d67a154a0527951d7f881 ^ permalink raw reply related [flat|nested] 25+ messages in thread
* [RFC PATCH v6 2/2] Bluetooth: L2CAP: tolerate zero-ident LE credit indications 2026-11-06 17:40 ` [RFC PATCH v6 1/2] " Kyle Pepper @ 2026-11-06 17:40 ` 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 2 siblings, 0 replies; 25+ messages in thread From: Kyle Pepper @ 2026-11-06 17:40 UTC (permalink / raw) To: linux-bluetooth; +Cc: luiz.dentz, marcel Cochlear Nucleus N8 captures contain LE credit indications with identifier zero. The specification forbids zero identifiers, so the current parser discards these grants and leaves the channel without usable transmit credits. Accept nonzero grants only for connected legacy LE CoC channels. Keep ECRED and all other commands excluded because the observed interoperability issue concerns legacy LE CoC. Apply normal credit overflow and pending-security handling, including the shared transmit guard from the preceding patch. Let the credit handler validate the exact payload size after the signaling parser validates framing. Assisted-by: LLM Signed-off-by: Kyle Pepper <cochcoder@proton.me> --- Changes in v6: - Rebase on the revised security patch; zero-ID source edits are unchanged. AI review of v5: https://sashiko.dev/#/patchset/20261106045515.853897-1-cochcoder%40proton.me Sashiko reported no findings for v5 patch 2. The legacy LE CoC exception still needs maintainer policy review. Native L2CAP and hardware behavior remain unverified. AI assistance: Codex prepared the revision and local tests. Attribution is retained. net/bluetooth/l2cap_core.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 24360d8e1..8d322d609 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -5401,6 +5401,10 @@ static inline int l2cap_le_credits(struct l2cap_conn *conn, chan->mode != L2CAP_MODE_EXT_FLOWCTL)) goto unlock; + /* Tolerate identifier-zero credits only for a live LE CoC. */ + if (!cmd->ident && (!credits || chan->mode != L2CAP_MODE_LE_FLOWCTL)) + goto unlock; + max_credits = LE_FLOWCTL_MAX_CREDITS - chan->tx_credits; if (credits > max_credits) { BT_ERR("LE credits overflow"); @@ -6010,11 +6014,20 @@ static inline void l2cap_le_sig_channel(struct l2cap_conn *conn, BT_DBG("code 0x%2.2x len %d id 0x%2.2x", cmd->code, len, cmd->ident); - if (len != skb->len || !cmd->ident) { + if (len != skb->len) { BT_DBG("corrupted command"); goto drop; } + if (!cmd->ident) { + if (cmd->code != L2CAP_LE_CREDITS) + goto drop; + + /* Credits have no response or identifier-matched transaction. */ + l2cap_le_credits(conn, cmd, len, skb->data); + goto drop; + } + err = l2cap_le_sig_cmd(conn, cmd, len, skb->data); if (err) { struct l2cap_cmd_rej_unk rej; ^ permalink raw reply related [flat|nested] 25+ messages in thread
* RE: [RFC,v6,1/2] Bluetooth: L2CAP: Keep credits from completing security upgrades 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 ` bluez.test.bot 2026-11-07 2:36 ` [RFC PATCH v7 1/2] " Kyle Pepper 2 siblings, 0 replies; 25+ messages in thread From: bluez.test.bot @ 2026-11-06 22:54 UTC (permalink / raw) To: linux-bluetooth, cochcoder [-- Attachment #1: Type: text/plain, Size: 2623 bytes --] This is automated email and please do not reply to this email! Dear submitter, Thank you for submitting the patches to the linux bluetooth mailing list. This is a CI test results with your patch series: PW Link:https://patchwork.kernel.org/series/1204403/ ---Test result--- Test Summary: CheckPatch FAIL 2.10 seconds VerifyFixes PASS 0.07 seconds VerifySignedoff PASS 0.07 seconds GitLint PASS 0.59 seconds SubjectPrefix PASS 0.13 seconds BuildKernel PASS 32.44 seconds CheckAllWarning PASS 36.82 seconds CheckSparse PASS 39.32 seconds BuildKernel32 PASS 32.47 seconds CheckKernelLLVM PASS 35.58 seconds TestRunnerSetup PASS 760.04 seconds TestRunner_l2cap-tester PASS 12.91 seconds TestRunner_smp-tester PASS 4.42 seconds IncrementalBuild PASS 43.00 seconds Details ############################## Test: CheckPatch - FAIL Desc: Run checkpatch.pl script Output: [RFC,v6,1/2] Bluetooth: L2CAP: Keep credits from completing security upgrades WARNING: Non-standard signature: Assisted-by: #106: Assisted-by: LLM ERROR: Unrecognized email address: 'LLM' #106: Assisted-by: LLM total: 1 errors, 1 warnings, 0 checks, 452 lines checked NOTE: For some of the reported defects, checkpatch may be able to mechanically convert to the typical style using --fix or --fix-inplace. /home/runner/work/bluetooth-next/bluetooth-next/src/patch/14958622.patch has style problems, please review. NOTE: Ignored message types: UNKNOWN_COMMIT_ID NOTE: If any of the errors are false positives, please report them to the maintainer, see CHECKPATCH in MAINTAINERS. [RFC,v6,2/2] Bluetooth: L2CAP: tolerate zero-ident LE credit indications WARNING: Non-standard signature: Assisted-by: #102: Assisted-by: LLM ERROR: Unrecognized email address: 'LLM' #102: Assisted-by: LLM total: 1 errors, 1 warnings, 0 checks, 31 lines checked NOTE: For some of the reported defects, checkpatch may be able to mechanically convert to the typical style using --fix or --fix-inplace. /home/runner/work/bluetooth-next/bluetooth-next/src/patch/14958623.patch has style problems, please review. NOTE: Ignored message types: UNKNOWN_COMMIT_ID NOTE: If any of the errors are false positives, please report them to the maintainer, see CHECKPATCH in MAINTAINERS. https://github.com/bluez/bluetooth-next/pull/1080 --- Regards, Linux Bluetooth ^ permalink raw reply [flat|nested] 25+ messages in thread
* [RFC PATCH v7 1/2] Bluetooth: L2CAP: Keep credits from completing security upgrades 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 ` 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 2 siblings, 2 replies; 25+ messages in thread From: Kyle Pepper @ 2026-11-07 2:36 UTC (permalink / raw) To: linux-bluetooth; +Cc: luiz.dentz, marcel 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 v6: - Own and drain an unbound security workqueue, with connection references and detach checks, instead of relying on a draining device workqueue. - Scope delayed SMP failure to older pending request generations; handle rollover and serialize producers, completion and BT_SECURITY. - Hold rejected ECRED children across close, use safe sibling iteration and child socket lock nesting, and finish whole-batch teardown. - Include zero DCID slots for refused children and select the synthetic non-deferred close fallback before batch handling. - Consume deferred errors and preserve an unread security rejection through teardown while waking ordinary error waiters. Previous version: https://lore.kernel.org/linux-bluetooth/20261106173948.596166-1-cochcoder@proton.me/ Both prefixes pass GCC/Clang sanitizer fixtures and six W=1 object builds. The two-CPU KASAN/PROVE_LOCKING guest passes 16 focused and 142 full traced cases, plus three untraced focused repeats. Worker timing is uncontrolled in native tests; forced delayed-work schedules are covered by mocks. The earlier local baseline labels were invalid: test-runner discarded the module-selection environment. Corrected controls select modules explicitly and verify file hashes and the actual loaded srcversion inside the guest. Unpatched base fails the zero-ID and deferred-response contract tests. Scan-close timing remains a separate test limitation. Base unload is not claimed; patched-target unload and pending-reference refusal are tested. Hardware, KCSAN, PTS, a fresh Sashiko verdict and remote CI remain unverified. AI assistance: Codex prepared the source, descriptions and validation. Required attribution remains; personal Linux DCO certification is separate. include/net/bluetooth/l2cap.h | 7 ++ net/bluetooth/l2cap_core.c | 213 ++++++++++++++++++++++++++++++++-- net/bluetooth/l2cap_sock.c | 164 +++++++++++++++++--------- net/bluetooth/smp.c | 1 + 4 files changed, 324 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..503942987 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,28 @@ 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); l2cap_check_encryption(chan, encrypt); l2cap_chan_unlock(chan); continue; @@ -8321,9 +8511,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 +8536,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); base-commit: ebdfe8561b5496b7eafedccd13a000a7261bc52b ^ permalink raw reply related [flat|nested] 25+ messages in thread
* [RFC PATCH v7 2/2] Bluetooth: L2CAP: tolerate zero-ident LE credit indications 2026-11-07 2:36 ` [RFC PATCH v7 1/2] " Kyle Pepper @ 2026-11-07 2:36 ` Kyle Pepper 2026-11-07 7:16 ` [RFC PATCH v8 1/2] Bluetooth: L2CAP: Keep credits from completing security upgrades Kyle Pepper 1 sibling, 0 replies; 25+ messages in thread From: Kyle Pepper @ 2026-11-07 2:36 UTC (permalink / raw) To: linux-bluetooth; +Cc: luiz.dentz, marcel Cochlear Nucleus N8 captures contain LE credit indications with identifier zero. The specification forbids zero identifiers, so the current parser discards these grants and leaves the channel without usable transmit credits. Accept nonzero grants only for connected legacy LE CoC channels. Keep ECRED and all other commands excluded because the observed interoperability issue concerns legacy LE CoC. Apply normal credit overflow and pending-security handling, including the shared transmit guard from the preceding patch. Let the credit handler validate the exact payload size after the signaling parser validates framing. Assisted-by: LLM Signed-off-by: Kyle Pepper <cochcoder@proton.me> --- Changes since v6: - Rebase on the revised security handling; zero-ID source edits are unchanged. Previous version: https://lore.kernel.org/linux-bluetooth/20261106173948.596166-1-cochcoder@proton.me/ Corrected native controls verify the loaded module's srcversion and SHA-256; the genuine unpatched base fails the single zero-identifier credit test. The concatenated-command rejection probe is not a patch-2 negative control. The legacy LE CoC exception still needs maintainer policy review. Hardware, PTS and new remote CI remain unverified. AI assistance: Codex prepared the revision and local tests. Attribution remains; personal Linux DCO certification is separate. net/bluetooth/l2cap_core.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 503942987..c25d339f9 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -5417,6 +5417,10 @@ static inline int l2cap_le_credits(struct l2cap_conn *conn, chan->mode != L2CAP_MODE_EXT_FLOWCTL)) goto unlock; + /* Tolerate identifier-zero credits only for a live LE CoC. */ + if (!cmd->ident && (!credits || chan->mode != L2CAP_MODE_LE_FLOWCTL)) + goto unlock; + max_credits = LE_FLOWCTL_MAX_CREDITS - chan->tx_credits; if (credits > max_credits) { BT_ERR("LE credits overflow"); @@ -6026,11 +6030,20 @@ static inline void l2cap_le_sig_channel(struct l2cap_conn *conn, BT_DBG("code 0x%2.2x len %d id 0x%2.2x", cmd->code, len, cmd->ident); - if (len != skb->len || !cmd->ident) { + if (len != skb->len) { BT_DBG("corrupted command"); goto drop; } + if (!cmd->ident) { + if (cmd->code != L2CAP_LE_CREDITS) + goto drop; + + /* Credits have no response or identifier-matched transaction. */ + l2cap_le_credits(conn, cmd, len, skb->data); + goto drop; + } + err = l2cap_le_sig_cmd(conn, cmd, len, skb->data); if (err) { struct l2cap_cmd_rej_unk rej; ^ permalink raw reply related [flat|nested] 25+ messages in thread
* [RFC PATCH v8 1/2] Bluetooth: L2CAP: Keep credits from completing security upgrades 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 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 1 sibling, 2 replies; 25+ messages in thread From: Kyle Pepper @ 2026-11-07 7:16 UTC (permalink / raw) To: linux-bluetooth; +Cc: luiz.dentz, marcel 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 ^ permalink raw reply related [flat|nested] 25+ messages in thread
* [RFC PATCH v8 2/2] Bluetooth: L2CAP: tolerate zero-ident LE credit indications 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 ` Kyle Pepper 2026-11-07 8:32 ` [RFC,v8,1/2] Bluetooth: L2CAP: Keep credits from completing security upgrades bluez.test.bot 1 sibling, 0 replies; 25+ messages in thread From: Kyle Pepper @ 2026-11-07 7:17 UTC (permalink / raw) To: linux-bluetooth; +Cc: luiz.dentz, marcel Cochlear Nucleus N8 captures contain LE credit indications with identifier zero. The specification forbids zero identifiers, so the current parser discards these grants and leaves the channel without usable transmit credits. Accept nonzero grants only for connected legacy LE CoC channels. Keep ECRED and all other commands excluded because the observed interoperability issue concerns legacy LE CoC. Apply normal credit overflow and pending-security handling, including the shared transmit guard from the preceding patch. Let the credit handler validate the exact payload size after the signaling parser validates framing. Assisted-by: LLM Signed-off-by: Kyle Pepper <cochcoder@proton.me> --- Changes since v7: no source changes; depends on patch 1. 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/ net/bluetooth/l2cap_core.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 3a7b71321..8fbdaac06 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -5417,6 +5417,10 @@ static inline int l2cap_le_credits(struct l2cap_conn *conn, chan->mode != L2CAP_MODE_EXT_FLOWCTL)) goto unlock; + /* Tolerate identifier-zero credits only for a live LE CoC. */ + if (!cmd->ident && (!credits || chan->mode != L2CAP_MODE_LE_FLOWCTL)) + goto unlock; + max_credits = LE_FLOWCTL_MAX_CREDITS - chan->tx_credits; if (credits > max_credits) { BT_ERR("LE credits overflow"); @@ -6026,11 +6030,20 @@ static inline void l2cap_le_sig_channel(struct l2cap_conn *conn, BT_DBG("code 0x%2.2x len %d id 0x%2.2x", cmd->code, len, cmd->ident); - if (len != skb->len || !cmd->ident) { + if (len != skb->len) { BT_DBG("corrupted command"); goto drop; } + if (!cmd->ident) { + if (cmd->code != L2CAP_LE_CREDITS) + goto drop; + + /* Credits have no response or identifier-matched transaction. */ + l2cap_le_credits(conn, cmd, len, skb->data); + goto drop; + } + err = l2cap_le_sig_cmd(conn, cmd, len, skb->data); if (err) { struct l2cap_cmd_rej_unk rej; -- 2.56.0 ^ permalink raw reply related [flat|nested] 25+ messages in thread
* RE: [RFC,v8,1/2] Bluetooth: L2CAP: Keep credits from completing security upgrades 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 ` bluez.test.bot 1 sibling, 0 replies; 25+ messages in thread From: bluez.test.bot @ 2026-11-07 8:32 UTC (permalink / raw) To: linux-bluetooth, cochcoder [-- Attachment #1: Type: text/plain, Size: 2973 bytes --] This is automated email and please do not reply to this email! Dear submitter, Thank you for submitting the patches to the linux bluetooth mailing list. This is a CI test results with your patch series: PW Link:https://patchwork.kernel.org/series/1204781/ ---Test result--- Test Summary: CheckPatch FAIL 1.78 seconds VerifyFixes PASS 0.06 seconds VerifySignedoff PASS 0.06 seconds GitLint PASS 0.85 seconds SubjectPrefix PASS 0.11 seconds BuildKernel PASS 23.47 seconds CheckAllWarning PASS 26.84 seconds CheckSparse PASS 29.32 seconds BuildKernel32 PASS 23.45 seconds CheckKernelLLVM PASS 26.09 seconds TestRunnerSetup PASS 551.67 seconds TestRunner_l2cap-tester FAIL 15.42 seconds TestRunner_smp-tester PASS 4.54 seconds IncrementalBuild PASS 29.41 seconds Details ############################## Test: CheckPatch - FAIL Desc: Run checkpatch.pl script Output: [RFC,v8,1/2] Bluetooth: L2CAP: Keep credits from completing security upgrades WARNING: Non-standard signature: Assisted-by: #127: Assisted-by: LLM ERROR: Unrecognized email address: 'LLM' #127: Assisted-by: LLM total: 1 errors, 1 warnings, 0 checks, 606 lines checked NOTE: For some of the reported defects, checkpatch may be able to mechanically convert to the typical style using --fix or --fix-inplace. /home/runner/work/bluetooth-next/bluetooth-next/src/patch/14960055.patch has style problems, please review. NOTE: Ignored message types: UNKNOWN_COMMIT_ID NOTE: If any of the errors are false positives, please report them to the maintainer, see CHECKPATCH in MAINTAINERS. [RFC,v8,2/2] Bluetooth: L2CAP: tolerate zero-ident LE credit indications WARNING: Non-standard signature: Assisted-by: #104: Assisted-by: LLM ERROR: Unrecognized email address: 'LLM' #104: Assisted-by: LLM total: 1 errors, 1 warnings, 0 checks, 31 lines checked NOTE: For some of the reported defects, checkpatch may be able to mechanically convert to the typical style using --fix or --fix-inplace. /home/runner/work/bluetooth-next/bluetooth-next/src/patch/14960056.patch has style problems, please review. NOTE: Ignored message types: UNKNOWN_COMMIT_ID NOTE: If any of the errors are false positives, please report them to the maintainer, see CHECKPATCH in MAINTAINERS. ############################## Test: TestRunner_l2cap-tester - FAIL Desc: Run l2cap-tester with test-runner Output: Total: 111, Passed: 109 (98.2%), Failed: 2, Not Run: 0 Failed Test Cases L2CAP Ext-Flowctl Server - Accept 2 Close 1 Timed out 2.006 seconds L2CAP LE EATT Server - Reject Failed 0.015 seconds https://github.com/bluez/bluetooth-next/pull/1084 --- Regards, Linux Bluetooth ^ permalink raw reply [flat|nested] 25+ messages in thread
end of thread, other threads:[~2026-11-07 8:32 UTC | newest] Thread overview: 25+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 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
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox