* [PATCH v4] Bluetooth: RFCOMM: connect the session socket without rfcomm_mutex
@ 2026-09-29 12:22 Mikhail Gavrilov
2026-09-29 13:16 ` Mikhail Gavrilov
` (2 more replies)
0 siblings, 3 replies; 5+ messages in thread
From: Mikhail Gavrilov @ 2026-09-29 12:22 UTC (permalink / raw)
To: marcel, luiz.dentz
Cc: nicoyip.dev, pav, r772577952, linux-bluetooth, linux-kernel,
Mikhail Gavrilov, syzbot+74071deb72339c215b2e
An RFCOMM connect() issued while a BR/EDR link is being authenticated
makes lockdep report a circular dependency, and the reported cycle is a
real AB/BA between rfcomm_mutex and hdev->lock.
rfcomm_security_cfm() is called from the HCI event path, which already
holds hdev->lock:
hci_rx_work()
hci_event_packet()
hci_cc_read_enc_key_size() [hdev->lock]
hci_encrypt_cfm() [hci_cb_list_lock]
rfcomm_security_cfm() [rfcomm_mutex]
while an RFCOMM connect() from userspace takes the same two locks the
other way round:
rfcomm_sock_connect()
rfcomm_dlc_open() [rfcomm_mutex]
__rfcomm_dlc_open()
rfcomm_session_create()
kernel_connect()
l2cap_sock_connect()
l2cap_chan_connect() [hdev->lock]
WARNING: possible circular locking dependency detected
kworker/u131:1/1128 is trying to acquire lock:
rfcomm_mutex, at: rfcomm_security_cfm+0x31/0x3e0 [rfcomm]
but task is already holding lock:
hci_cb_list_lock, at: hci_cc_read_enc_key_size+0x1d2/0xcc0
Chain exists of:
rfcomm_mutex --> &hdev->lock --> hci_cb_list_lock
hci_auth_complete_evt() and hci_encrypt_change_evt() reach the callback
the same way.
Both orders have to be seen in the same boot, which is why a BR/EDR
connection alone is not enough to show it: a session set up by the
remote side is created by rfcomm_accept_connection() in krfcommd, which
calls kernel_accept() and never takes hdev->lock under rfcomm_mutex.
Connecting a device that authenticates and encrypts the link and then
calling connect() on an RFCOMM socket towards any address - the connect
does not have to succeed, the order is recorded before the page timeout
- reports it every time.
Only the session socket has to be connected with the lock held, and it
does not: nothing else can see the socket before it is put on the
session list. So connect it first and take rfcomm_mutex afterwards,
which removes the rfcomm_mutex -> hdev->lock order for good, rather
than keeping the HCI event path out of rfcomm_mutex.
rfcomm_session_create() becomes rfcomm_session_connect(), which returns
the connected socket without touching the session list, and
rfcomm_dlc_open() adds the session once it holds the lock again. If
another opener added a session for the same pair while this socket was
connecting, that session is used and this socket is dropped.
__rfcomm_dlc_open() now takes the session it should use, and its state
check runs after the lock is re-acquired, so a DLC that was opened or
closed in the meantime is still handled.
Over an existing ACL link the connection can complete before the
session reaches the list, and the wakeup from the socket callback is
then lost, so krfcommd is woken once the session is visible.
Connecting without the lock opens a window in which the socket can be
closed. The DLC is not on a session yet, so rfcomm_dlc_close() finds
nothing to do and returns without touching it, and rfcomm_dlc_open()
would then attach a DLC whose owner is gone. rfcomm_dlc_close() now
marks such a DLC with RFCOMM_CLOSED, and rfcomm_dlc_open() checks the
mark once it holds rfcomm_mutex again and drops the socket instead of
attaching the DLC. The mark is cleared when an open starts, so a DLC
that is reused is not affected. This covers both callers of
rfcomm_dlc_open(), the socket and the tty layer.
Fixes: 759c185d0bbd ("Bluetooth: RFCOMM: serialize security confirmation handling")
Suggested-by: Pauli Virtanen <pav@iki.fi>
Reported-by: Pauli Virtanen <pav@iki.fi>
Closes: https://lore.kernel.org/linux-bluetooth/5e76a95e934e451e7006db28827c2d64af5a88be.camel@iki.fi/
Reported-by: syzbot+74071deb72339c215b2e@syzkaller.appspotmail.com
Closes: https://lore.kernel.org/linux-bluetooth/6a92fadc.08e933ee.dbf97.008f.GAE@google.com/
Reported-by: Jiaming Zhang <r772577952@gmail.com>
Closes: https://lore.kernel.org/all/CANypQFabseTuRiyz2pGkc_Qj0moGDnY7KEp1qdTOqkw6gAL3hw@mail.gmail.com/
Cc: stable@vger.kernel.org
Signed-off-by: Mikhail Gavrilov <mikhail.v.gavrilov@gmail.com>
---
The commit this fixes is in v7.3-rc1 and is marked for stable, so this
probably wants the bluetooth fixes tree rather than -next.
v4:
- rfcomm_dlc_close() marks a DLC that is not on a session yet, and
rfcomm_dlc_open() drops the socket instead of attaching the DLC when
it finds the mark after taking rfcomm_mutex again (Luiz, and the
Sashiko review of v3)
- Reported-by/Closes for Jiaming Zhang's report, which hits the same
cycle from the connect side: rfcomm_dlc_open() holding rfcomm_mutex
while l2cap_chan_connect() takes hdev->lock
- syzbot tested v3 with its reproducer, no issue:
https://lore.kernel.org/all/6ab100d7.71f81b7d.15fa6d.0029.GAE@google.com/
Tested on 7.3.0-rc5 with an MT7922 controller (btusb) and a JBL Tour
Pro 3 headset. An RFCOMM connect() towards an address that does not
answer, run after the headset has authenticated its link, reports the
inversion without the patch (checked on 7.3-rc2); with v4 it stays quiet
and the validator is still armed afterwards (debug_locks: 1). A connect
towards the connected headset is answered in 23 ms with ECONNREFUSED -
the session is established over the existing ACL link and the peer
rejects the channel - and towards an idle one it fails with EHOSTDOWN
after the page timeout. The reproducer is in the v3 posting.
v3: https://lore.kernel.org/linux-bluetooth/20260912100315.151674-1-mikhail.v.gavrilov@gmail.com/
v2: https://lore.kernel.org/linux-bluetooth/20260904012028.77590-1-mikhail.v.gavrilov@gmail.com/
v1: https://lore.kernel.org/linux-bluetooth/20260902235132.453044-1-mikhail.v.gavrilov@gmail.com/
include/net/bluetooth/rfcomm.h | 1 +
net/bluetooth/rfcomm/core.c | 134 +++++++++++++++++++++++----------
2 files changed, 96 insertions(+), 39 deletions(-)
diff --git a/include/net/bluetooth/rfcomm.h b/include/net/bluetooth/rfcomm.h
index 102c278e3..9dc5fd4cb 100644
--- a/include/net/bluetooth/rfcomm.h
+++ b/include/net/bluetooth/rfcomm.h
@@ -207,6 +207,7 @@ struct rfcomm_dlc {
#define RFCOMM_AUTH_REJECT 7
#define RFCOMM_DEFER_SETUP 8
#define RFCOMM_ENC_DROP 9
+#define RFCOMM_CLOSED 10
/* Scheduling flags and events */
#define RFCOMM_SCHED_WAKEUP 31
diff --git a/net/bluetooth/rfcomm/core.c b/net/bluetooth/rfcomm/core.c
index d91e2a6ee..54ec1136f 100644
--- a/net/bluetooth/rfcomm/core.c
+++ b/net/bluetooth/rfcomm/core.c
@@ -62,10 +62,9 @@ static void rfcomm_make_uih(struct sk_buff *skb, u8 addr);
static void rfcomm_process_connect(struct rfcomm_session *s);
-static struct rfcomm_session *rfcomm_session_create(bdaddr_t *src,
- bdaddr_t *dst,
- u8 sec_level,
- int *err);
+static struct socket *rfcomm_session_connect(bdaddr_t *src, bdaddr_t *dst,
+ u8 sec_level, int *err);
+static struct rfcomm_session *rfcomm_session_add(struct socket *sock, int state);
static struct rfcomm_session *rfcomm_session_get(bdaddr_t *src, bdaddr_t *dst);
static struct rfcomm_session *rfcomm_session_del(struct rfcomm_session *s);
@@ -365,28 +364,17 @@ static int rfcomm_check_channel(u8 channel)
return channel < 1 || channel > 30;
}
-static int __rfcomm_dlc_open(struct rfcomm_dlc *d, bdaddr_t *src, bdaddr_t *dst, u8 channel)
+static int __rfcomm_dlc_open(struct rfcomm_dlc *d, struct rfcomm_session *s,
+ u8 channel)
{
- struct rfcomm_session *s;
- int err = 0;
u8 dlci;
- BT_DBG("dlc %p state %ld %pMR -> %pMR channel %d",
- d, d->state, src, dst, channel);
-
- if (rfcomm_check_channel(channel))
- return -EINVAL;
+ BT_DBG("dlc %p state %ld session %p channel %d",
+ d, d->state, s, channel);
if (d->state != BT_OPEN && d->state != BT_CLOSED)
return 0;
- s = rfcomm_session_get(src, dst);
- if (!s) {
- s = rfcomm_session_create(src, dst, d->sec_level, &err);
- if (!s)
- return err;
- }
-
dlci = __dlci(__session_dir(s), channel);
/* Check if DLCI already exists */
@@ -421,14 +409,84 @@ static int __rfcomm_dlc_open(struct rfcomm_dlc *d, bdaddr_t *src, bdaddr_t *dst,
int rfcomm_dlc_open(struct rfcomm_dlc *d, bdaddr_t *src, bdaddr_t *dst, u8 channel)
{
- int r;
+ struct rfcomm_session *s;
+ struct socket *sock;
+ int err;
+
+ BT_DBG("dlc %p state %ld %pMR -> %pMR channel %d",
+ d, d->state, src, dst, channel);
+
+ if (rfcomm_check_channel(channel))
+ return -EINVAL;
rfcomm_lock();
- r = __rfcomm_dlc_open(d, src, dst, channel);
+ clear_bit(RFCOMM_CLOSED, &d->flags);
+ /* Do not page the remote device for a DLC that cannot be opened
+ * anyway. __rfcomm_dlc_open() looks at the state again once the
+ * lock has been re-acquired below.
+ */
+ if (d->state != BT_OPEN && d->state != BT_CLOSED) {
+ rfcomm_unlock();
+ return 0;
+ }
+
+ s = rfcomm_session_get(src, dst);
+ if (s) {
+ err = __rfcomm_dlc_open(d, s, channel);
+ rfcomm_unlock();
+ return err;
+ }
rfcomm_unlock();
- return r;
+
+ /* There is no session for this pair yet. kernel_connect() ends up in
+ * l2cap_chan_connect(), which takes hdev->lock, and the HCI event
+ * path takes rfcomm_mutex while holding hdev->lock, so the socket has
+ * to be connected with rfcomm_mutex released.
+ */
+ sock = rfcomm_session_connect(src, dst, d->sec_level, &err);
+ if (!sock)
+ return err;
+
+ rfcomm_lock();
+
+ /* The DLC may have been closed while the socket was connecting. It
+ * was not on a session, so rfcomm_dlc_close() could only mark it;
+ * attaching it now would leave it with no owner.
+ */
+ if (test_bit(RFCOMM_CLOSED, &d->flags)) {
+ rfcomm_unlock();
+ sock_release(sock);
+ return -ECONNRESET;
+ }
+
+ /* Another opener may have added a session for the same pair in the
+ * meantime; that one is used and this socket is dropped.
+ */
+ s = rfcomm_session_get(src, dst);
+ if (!s) {
+ s = rfcomm_session_add(sock, BT_BOUND);
+ if (s) {
+ s->initiator = 1;
+ sock = NULL;
+ }
+ }
+
+ err = s ? __rfcomm_dlc_open(d, s, channel) : -ENOMEM;
+
+ rfcomm_unlock();
+
+ if (sock)
+ sock_release(sock);
+
+ /* Over an existing ACL link the connection can complete before the
+ * session reaches the list, and that wakeup is then lost, so let
+ * krfcommd look at the socket state now.
+ */
+ rfcomm_schedule();
+
+ return err;
}
static void __rfcomm_dlc_disconn(struct rfcomm_dlc *d)
@@ -508,8 +566,14 @@ int rfcomm_dlc_close(struct rfcomm_dlc *d, int err)
rfcomm_lock();
s = d->session;
- if (!s)
+ if (!s) {
+ /* Not on a session yet: rfcomm_dlc_open() may be connecting a
+ * socket for it with rfcomm_mutex released. Leave a mark so
+ * that it does not attach the DLC once it holds the lock again.
+ */
+ set_bit(RFCOMM_CLOSED, &d->flags);
goto no_session;
+ }
/* after waiting on the mutex check the session still exists
* then check the dlc still exists
@@ -757,12 +821,12 @@ static struct rfcomm_session *rfcomm_session_close(struct rfcomm_session *s,
return rfcomm_session_del(s);
}
-static struct rfcomm_session *rfcomm_session_create(bdaddr_t *src,
- bdaddr_t *dst,
- u8 sec_level,
- int *err)
+/* Creates the L2CAP socket a new session will run on and starts connecting
+ * it. Must be called with rfcomm_mutex released.
+ */
+static struct socket *rfcomm_session_connect(bdaddr_t *src, bdaddr_t *dst,
+ u8 sec_level, int *err)
{
- struct rfcomm_session *s = NULL;
struct sockaddr_l2 addr;
struct socket *sock;
struct sock *sk;
@@ -792,24 +856,16 @@ static struct rfcomm_session *rfcomm_session_create(bdaddr_t *src,
l2cap_pi(sk)->chan->mode = L2CAP_MODE_ERTM;
release_sock(sk);
- s = rfcomm_session_add(sock, BT_BOUND);
- if (!s) {
- *err = -ENOMEM;
- goto failed;
- }
-
- s->initiator = 1;
-
bacpy(&addr.l2_bdaddr, dst);
addr.l2_family = AF_BLUETOOTH;
addr.l2_psm = cpu_to_le16(L2CAP_PSM_RFCOMM);
addr.l2_cid = 0;
addr.l2_bdaddr_type = BDADDR_BREDR;
*err = kernel_connect(sock, (struct sockaddr_unsized *)&addr, sizeof(addr), O_NONBLOCK);
- if (*err == 0 || *err == -EINPROGRESS)
- return s;
+ if (*err && *err != -EINPROGRESS)
+ goto failed;
- return rfcomm_session_del(s);
+ return sock;
failed:
sock_release(sock);
--
2.43.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v4] Bluetooth: RFCOMM: connect the session socket without rfcomm_mutex
2026-09-29 12:22 [PATCH v4] Bluetooth: RFCOMM: connect the session socket without rfcomm_mutex Mikhail Gavrilov
@ 2026-09-29 13:16 ` Mikhail Gavrilov
2026-09-29 14:19 ` Jiaming Zhang
2026-09-29 15:10 ` patchwork-bot+bluetooth
2 siblings, 0 replies; 5+ messages in thread
From: Mikhail Gavrilov @ 2026-09-29 13:16 UTC (permalink / raw)
To: marcel, luiz.dentz
Cc: nicoyip.dev, pav, r772577952, linux-bluetooth, linux-kernel,
syzbot+74071deb72339c215b2e
On Tue, Sep 29, 2026 at 5:22 PM Mikhail Gavrilov
<mikhail.v.gavrilov@gmail.com> wrote:
>
> An RFCOMM connect() issued while a BR/EDR link is being authenticated
> makes lockdep report a circular dependency, and the reported cycle is a
> real AB/BA between rfcomm_mutex and hdev->lock.
syzbot tested this version against its reproducer on v7.3-rc5, no issue:
https://lore.kernel.org/all/6abbb59e.64639fcc.9812d.0003.GAE@google.com/
Tested-by: syzbot+74071deb72339c215b2e@syzkaller.appspotmail.com
--
Thanks,
Mikhail.
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v4] Bluetooth: RFCOMM: connect the session socket without rfcomm_mutex
2026-09-29 12:22 [PATCH v4] Bluetooth: RFCOMM: connect the session socket without rfcomm_mutex Mikhail Gavrilov
2026-09-29 13:16 ` Mikhail Gavrilov
@ 2026-09-29 14:19 ` Jiaming Zhang
2026-09-29 15:10 ` patchwork-bot+bluetooth
2 siblings, 0 replies; 5+ messages in thread
From: Jiaming Zhang @ 2026-09-29 14:19 UTC (permalink / raw)
To: Mikhail Gavrilov
Cc: marcel, luiz.dentz, nicoyip.dev, pav, linux-bluetooth,
linux-kernel, syzbot+74071deb72339c215b2e
Mikhail Gavrilov <mikhail.v.gavrilov@gmail.com> 于2026年9月29日周二 20:22写道:
>
> An RFCOMM connect() issued while a BR/EDR link is being authenticated
> makes lockdep report a circular dependency, and the reported cycle is a
> real AB/BA between rfcomm_mutex and hdev->lock.
>
> rfcomm_security_cfm() is called from the HCI event path, which already
> holds hdev->lock:
>
> hci_rx_work()
> hci_event_packet()
> hci_cc_read_enc_key_size() [hdev->lock]
> hci_encrypt_cfm() [hci_cb_list_lock]
> rfcomm_security_cfm() [rfcomm_mutex]
>
> while an RFCOMM connect() from userspace takes the same two locks the
> other way round:
>
> rfcomm_sock_connect()
> rfcomm_dlc_open() [rfcomm_mutex]
> __rfcomm_dlc_open()
> rfcomm_session_create()
> kernel_connect()
> l2cap_sock_connect()
> l2cap_chan_connect() [hdev->lock]
>
> WARNING: possible circular locking dependency detected
> kworker/u131:1/1128 is trying to acquire lock:
> rfcomm_mutex, at: rfcomm_security_cfm+0x31/0x3e0 [rfcomm]
> but task is already holding lock:
> hci_cb_list_lock, at: hci_cc_read_enc_key_size+0x1d2/0xcc0
> Chain exists of:
> rfcomm_mutex --> &hdev->lock --> hci_cb_list_lock
>
> hci_auth_complete_evt() and hci_encrypt_change_evt() reach the callback
> the same way.
>
> Both orders have to be seen in the same boot, which is why a BR/EDR
> connection alone is not enough to show it: a session set up by the
> remote side is created by rfcomm_accept_connection() in krfcommd, which
> calls kernel_accept() and never takes hdev->lock under rfcomm_mutex.
> Connecting a device that authenticates and encrypts the link and then
> calling connect() on an RFCOMM socket towards any address - the connect
> does not have to succeed, the order is recorded before the page timeout
> - reports it every time.
>
> Only the session socket has to be connected with the lock held, and it
> does not: nothing else can see the socket before it is put on the
> session list. So connect it first and take rfcomm_mutex afterwards,
> which removes the rfcomm_mutex -> hdev->lock order for good, rather
> than keeping the HCI event path out of rfcomm_mutex.
>
> rfcomm_session_create() becomes rfcomm_session_connect(), which returns
> the connected socket without touching the session list, and
> rfcomm_dlc_open() adds the session once it holds the lock again. If
> another opener added a session for the same pair while this socket was
> connecting, that session is used and this socket is dropped.
> __rfcomm_dlc_open() now takes the session it should use, and its state
> check runs after the lock is re-acquired, so a DLC that was opened or
> closed in the meantime is still handled.
>
> Over an existing ACL link the connection can complete before the
> session reaches the list, and the wakeup from the socket callback is
> then lost, so krfcommd is woken once the session is visible.
>
> Connecting without the lock opens a window in which the socket can be
> closed. The DLC is not on a session yet, so rfcomm_dlc_close() finds
> nothing to do and returns without touching it, and rfcomm_dlc_open()
> would then attach a DLC whose owner is gone. rfcomm_dlc_close() now
> marks such a DLC with RFCOMM_CLOSED, and rfcomm_dlc_open() checks the
> mark once it holds rfcomm_mutex again and drops the socket instead of
> attaching the DLC. The mark is cleared when an open starts, so a DLC
> that is reused is not affected. This covers both callers of
> rfcomm_dlc_open(), the socket and the tty layer.
>
> Fixes: 759c185d0bbd ("Bluetooth: RFCOMM: serialize security confirmation handling")
> Suggested-by: Pauli Virtanen <pav@iki.fi>
> Reported-by: Pauli Virtanen <pav@iki.fi>
> Closes: https://lore.kernel.org/linux-bluetooth/5e76a95e934e451e7006db28827c2d64af5a88be.camel@iki.fi/
> Reported-by: syzbot+74071deb72339c215b2e@syzkaller.appspotmail.com
> Closes: https://lore.kernel.org/linux-bluetooth/6a92fadc.08e933ee.dbf97.008f.GAE@google.com/
> Reported-by: Jiaming Zhang <r772577952@gmail.com>
> Closes: https://lore.kernel.org/all/CANypQFabseTuRiyz2pGkc_Qj0moGDnY7KEp1qdTOqkw6gAL3hw@mail.gmail.com/
> Cc: stable@vger.kernel.org
> Signed-off-by: Mikhail Gavrilov <mikhail.v.gavrilov@gmail.com>
> ---
>
> The commit this fixes is in v7.3-rc1 and is marked for stable, so this
> probably wants the bluetooth fixes tree rather than -next.
>
> v4:
> - rfcomm_dlc_close() marks a DLC that is not on a session yet, and
> rfcomm_dlc_open() drops the socket instead of attaching the DLC when
> it finds the mark after taking rfcomm_mutex again (Luiz, and the
> Sashiko review of v3)
> - Reported-by/Closes for Jiaming Zhang's report, which hits the same
> cycle from the connect side: rfcomm_dlc_open() holding rfcomm_mutex
> while l2cap_chan_connect() takes hdev->lock
> - syzbot tested v3 with its reproducer, no issue:
> https://lore.kernel.org/all/6ab100d7.71f81b7d.15fa6d.0029.GAE@google.com/
>
> Tested on 7.3.0-rc5 with an MT7922 controller (btusb) and a JBL Tour
> Pro 3 headset. An RFCOMM connect() towards an address that does not
> answer, run after the headset has authenticated its link, reports the
> inversion without the patch (checked on 7.3-rc2); with v4 it stays quiet
> and the validator is still armed afterwards (debug_locks: 1). A connect
> towards the connected headset is answered in 23 ms with ECONNREFUSED -
> the session is established over the existing ACL link and the peer
> rejects the channel - and towards an idle one it fails with EHOSTDOWN
> after the page timeout. The reproducer is in the v3 posting.
>
> v3: https://lore.kernel.org/linux-bluetooth/20260912100315.151674-1-mikhail.v.gavrilov@gmail.com/
> v2: https://lore.kernel.org/linux-bluetooth/20260904012028.77590-1-mikhail.v.gavrilov@gmail.com/
> v1: https://lore.kernel.org/linux-bluetooth/20260902235132.453044-1-mikhail.v.gavrilov@gmail.com/
>
> include/net/bluetooth/rfcomm.h | 1 +
> net/bluetooth/rfcomm/core.c | 134 +++++++++++++++++++++++----------
> 2 files changed, 96 insertions(+), 39 deletions(-)
>
> diff --git a/include/net/bluetooth/rfcomm.h b/include/net/bluetooth/rfcomm.h
> index 102c278e3..9dc5fd4cb 100644
> --- a/include/net/bluetooth/rfcomm.h
> +++ b/include/net/bluetooth/rfcomm.h
> @@ -207,6 +207,7 @@ struct rfcomm_dlc {
> #define RFCOMM_AUTH_REJECT 7
> #define RFCOMM_DEFER_SETUP 8
> #define RFCOMM_ENC_DROP 9
> +#define RFCOMM_CLOSED 10
>
> /* Scheduling flags and events */
> #define RFCOMM_SCHED_WAKEUP 31
> diff --git a/net/bluetooth/rfcomm/core.c b/net/bluetooth/rfcomm/core.c
> index d91e2a6ee..54ec1136f 100644
> --- a/net/bluetooth/rfcomm/core.c
> +++ b/net/bluetooth/rfcomm/core.c
> @@ -62,10 +62,9 @@ static void rfcomm_make_uih(struct sk_buff *skb, u8 addr);
>
> static void rfcomm_process_connect(struct rfcomm_session *s);
>
> -static struct rfcomm_session *rfcomm_session_create(bdaddr_t *src,
> - bdaddr_t *dst,
> - u8 sec_level,
> - int *err);
> +static struct socket *rfcomm_session_connect(bdaddr_t *src, bdaddr_t *dst,
> + u8 sec_level, int *err);
> +static struct rfcomm_session *rfcomm_session_add(struct socket *sock, int state);
> static struct rfcomm_session *rfcomm_session_get(bdaddr_t *src, bdaddr_t *dst);
> static struct rfcomm_session *rfcomm_session_del(struct rfcomm_session *s);
>
> @@ -365,28 +364,17 @@ static int rfcomm_check_channel(u8 channel)
> return channel < 1 || channel > 30;
> }
>
> -static int __rfcomm_dlc_open(struct rfcomm_dlc *d, bdaddr_t *src, bdaddr_t *dst, u8 channel)
> +static int __rfcomm_dlc_open(struct rfcomm_dlc *d, struct rfcomm_session *s,
> + u8 channel)
> {
> - struct rfcomm_session *s;
> - int err = 0;
> u8 dlci;
>
> - BT_DBG("dlc %p state %ld %pMR -> %pMR channel %d",
> - d, d->state, src, dst, channel);
> -
> - if (rfcomm_check_channel(channel))
> - return -EINVAL;
> + BT_DBG("dlc %p state %ld session %p channel %d",
> + d, d->state, s, channel);
>
> if (d->state != BT_OPEN && d->state != BT_CLOSED)
> return 0;
>
> - s = rfcomm_session_get(src, dst);
> - if (!s) {
> - s = rfcomm_session_create(src, dst, d->sec_level, &err);
> - if (!s)
> - return err;
> - }
> -
> dlci = __dlci(__session_dir(s), channel);
>
> /* Check if DLCI already exists */
> @@ -421,14 +409,84 @@ static int __rfcomm_dlc_open(struct rfcomm_dlc *d, bdaddr_t *src, bdaddr_t *dst,
>
> int rfcomm_dlc_open(struct rfcomm_dlc *d, bdaddr_t *src, bdaddr_t *dst, u8 channel)
> {
> - int r;
> + struct rfcomm_session *s;
> + struct socket *sock;
> + int err;
> +
> + BT_DBG("dlc %p state %ld %pMR -> %pMR channel %d",
> + d, d->state, src, dst, channel);
> +
> + if (rfcomm_check_channel(channel))
> + return -EINVAL;
>
> rfcomm_lock();
>
> - r = __rfcomm_dlc_open(d, src, dst, channel);
> + clear_bit(RFCOMM_CLOSED, &d->flags);
>
> + /* Do not page the remote device for a DLC that cannot be opened
> + * anyway. __rfcomm_dlc_open() looks at the state again once the
> + * lock has been re-acquired below.
> + */
> + if (d->state != BT_OPEN && d->state != BT_CLOSED) {
> + rfcomm_unlock();
> + return 0;
> + }
> +
> + s = rfcomm_session_get(src, dst);
> + if (s) {
> + err = __rfcomm_dlc_open(d, s, channel);
> + rfcomm_unlock();
> + return err;
> + }
> rfcomm_unlock();
> - return r;
> +
> + /* There is no session for this pair yet. kernel_connect() ends up in
> + * l2cap_chan_connect(), which takes hdev->lock, and the HCI event
> + * path takes rfcomm_mutex while holding hdev->lock, so the socket has
> + * to be connected with rfcomm_mutex released.
> + */
> + sock = rfcomm_session_connect(src, dst, d->sec_level, &err);
> + if (!sock)
> + return err;
> +
> + rfcomm_lock();
> +
> + /* The DLC may have been closed while the socket was connecting. It
> + * was not on a session, so rfcomm_dlc_close() could only mark it;
> + * attaching it now would leave it with no owner.
> + */
> + if (test_bit(RFCOMM_CLOSED, &d->flags)) {
> + rfcomm_unlock();
> + sock_release(sock);
> + return -ECONNRESET;
> + }
> +
> + /* Another opener may have added a session for the same pair in the
> + * meantime; that one is used and this socket is dropped.
> + */
> + s = rfcomm_session_get(src, dst);
> + if (!s) {
> + s = rfcomm_session_add(sock, BT_BOUND);
> + if (s) {
> + s->initiator = 1;
> + sock = NULL;
> + }
> + }
> +
> + err = s ? __rfcomm_dlc_open(d, s, channel) : -ENOMEM;
> +
> + rfcomm_unlock();
> +
> + if (sock)
> + sock_release(sock);
> +
> + /* Over an existing ACL link the connection can complete before the
> + * session reaches the list, and that wakeup is then lost, so let
> + * krfcommd look at the socket state now.
> + */
> + rfcomm_schedule();
> +
> + return err;
> }
>
> static void __rfcomm_dlc_disconn(struct rfcomm_dlc *d)
> @@ -508,8 +566,14 @@ int rfcomm_dlc_close(struct rfcomm_dlc *d, int err)
> rfcomm_lock();
>
> s = d->session;
> - if (!s)
> + if (!s) {
> + /* Not on a session yet: rfcomm_dlc_open() may be connecting a
> + * socket for it with rfcomm_mutex released. Leave a mark so
> + * that it does not attach the DLC once it holds the lock again.
> + */
> + set_bit(RFCOMM_CLOSED, &d->flags);
> goto no_session;
> + }
>
> /* after waiting on the mutex check the session still exists
> * then check the dlc still exists
> @@ -757,12 +821,12 @@ static struct rfcomm_session *rfcomm_session_close(struct rfcomm_session *s,
> return rfcomm_session_del(s);
> }
>
> -static struct rfcomm_session *rfcomm_session_create(bdaddr_t *src,
> - bdaddr_t *dst,
> - u8 sec_level,
> - int *err)
> +/* Creates the L2CAP socket a new session will run on and starts connecting
> + * it. Must be called with rfcomm_mutex released.
> + */
> +static struct socket *rfcomm_session_connect(bdaddr_t *src, bdaddr_t *dst,
> + u8 sec_level, int *err)
> {
> - struct rfcomm_session *s = NULL;
> struct sockaddr_l2 addr;
> struct socket *sock;
> struct sock *sk;
> @@ -792,24 +856,16 @@ static struct rfcomm_session *rfcomm_session_create(bdaddr_t *src,
> l2cap_pi(sk)->chan->mode = L2CAP_MODE_ERTM;
> release_sock(sk);
>
> - s = rfcomm_session_add(sock, BT_BOUND);
> - if (!s) {
> - *err = -ENOMEM;
> - goto failed;
> - }
> -
> - s->initiator = 1;
> -
> bacpy(&addr.l2_bdaddr, dst);
> addr.l2_family = AF_BLUETOOTH;
> addr.l2_psm = cpu_to_le16(L2CAP_PSM_RFCOMM);
> addr.l2_cid = 0;
> addr.l2_bdaddr_type = BDADDR_BREDR;
> *err = kernel_connect(sock, (struct sockaddr_unsized *)&addr, sizeof(addr), O_NONBLOCK);
> - if (*err == 0 || *err == -EINPROGRESS)
> - return s;
> + if (*err && *err != -EINPROGRESS)
> + goto failed;
>
> - return rfcomm_session_del(s);
> + return sock;
>
> failed:
> sock_release(sock);
> --
> 2.43.0
>
After applying the patch and running my reproducer, no issues were triggered.
Tested-by: Jiaming Zhang <r772577952@gmail.com>
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v4] Bluetooth: RFCOMM: connect the session socket without rfcomm_mutex
2026-09-29 12:22 [PATCH v4] Bluetooth: RFCOMM: connect the session socket without rfcomm_mutex Mikhail Gavrilov
2026-09-29 13:16 ` Mikhail Gavrilov
2026-09-29 14:19 ` Jiaming Zhang
@ 2026-09-29 15:10 ` patchwork-bot+bluetooth
2 siblings, 0 replies; 5+ messages in thread
From: patchwork-bot+bluetooth @ 2026-09-29 15:10 UTC (permalink / raw)
To: Mikhail Gavrilov
Cc: marcel, luiz.dentz, nicoyip.dev, pav, r772577952,
linux-bluetooth, linux-kernel, syzbot+74071deb72339c215b2e
Hello:
This patch was applied to bluetooth/bluetooth-next.git (master)
by Luiz Augusto von Dentz <luiz.von.dentz@intel.com>:
On Tue, 29 Sep 2026 17:22:26 +0500 you wrote:
> An RFCOMM connect() issued while a BR/EDR link is being authenticated
> makes lockdep report a circular dependency, and the reported cycle is a
> real AB/BA between rfcomm_mutex and hdev->lock.
>
> rfcomm_security_cfm() is called from the HCI event path, which already
> holds hdev->lock:
>
> [...]
Here is the summary with links:
- [v4] Bluetooth: RFCOMM: connect the session socket without rfcomm_mutex
https://git.kernel.org/bluetooth/bluetooth-next/c/ac022970e9fb
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v4] Bluetooth: RFCOMM: connect the session socket without rfcomm_mutex
@ 2026-10-11 14:47 Eugene Siegel
0 siblings, 0 replies; 5+ messages in thread
From: Eugene Siegel @ 2026-10-11 14:47 UTC (permalink / raw)
To: patchwork-bot+bluetooth
Cc: linux-bluetooth, linux-kernel, Luiz Augusto von Dentz, marcel,
mikhail.v.gavrilov, nicoyip.dev, pav, r772577952,
syzbot+74071deb72339c215b2e
Hi,
With this patch on 3857c2fe54, I can hit a similar deadlock in
rfcomm_security_cfm.
Eugene Siegel
======================================================
WARNING: possible circular locking dependency detected
7.3.0-rc6 #1-NixOS Not tainted
------------------------------------------------------
kworker/u5:2/178 is trying to acquire lock:
ffffffff8eff81c0 (rfcomm_mutex){+.+.}-{4:4}, at:
rfcomm_security_cfm+0x37/0x5a0 net/bluetooth/rfcomm/core.c:2279
but task is already holding lock:
ffffffff8efe4d60 (hci_cb_list_lock){+.+.}-{4:4}, at:
hci_encrypt_cfm+0x4ab/0x720 include/net/bluetooth/hci_core.h:2240
which lock already depends on the new lock.
the existing dependency chain (in reverse order) is:
-> #2 (hci_cb_list_lock){+.+.}-{4:4}:
(inlined __mutex_lock_common kernel/locking/mutex.c:646)
__mutex_lock+0x19c/0x1620 kernel/locking/mutex.c:821
(inlined hci_connect_cfm include/net/bluetooth/hci_core.h:2164)
hci_remote_features_evt+0x35b/0x8a0 net/bluetooth/hci_event.c:3786
(inlined hci_event_func net/bluetooth/hci_event.c:7887)
hci_event_packet+0x7f1/0xe10 net/bluetooth/hci_event.c:7938
hci_rx_work+0x3cc/0x8d0 net/bluetooth/hci_core.c:4087
(inlined process_one_work kernel/workqueue.c:3396)
process_scheduled_works+0xc99/0x1900 kernel/workqueue.c:3479
worker_thread+0x8a2/0xda0 kernel/workqueue.c:3560
kthread+0x38b/0x470 kernel/kthread.c:436
ret_from_fork+0x43b/0x8f0 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
-> #1 (&hdev->lock){+.+.}-{4:4}:
(inlined __mutex_lock_common kernel/locking/mutex.c:646)
__mutex_lock+0x19c/0x1620 kernel/locking/mutex.c:821
hci_release_dev+0x123/0x14b0 net/bluetooth/hci_core.c:2731
bt_host_release+0x93/0xa0 net/bluetooth/hci_sysfs.c:100
device_release+0xc4/0x1f0 drivers/base/core.c:2644
(inlined kobject_cleanup lib/kobject.c:689)
(inlined kobject_release lib/kobject.c:720)
(inlined kref_put include/linux/kref.h:65)
kobject_put+0x16c/0x270 lib/kobject.c:737
device_release+0xc4/0x1f0 drivers/base/core.c:2644
(inlined kobject_cleanup lib/kobject.c:689)
(inlined kobject_release lib/kobject.c:720)
(inlined kref_put include/linux/kref.h:65)
kobject_put+0x16c/0x270 lib/kobject.c:737
(inlined hci_conn_put include/net/bluetooth/hci_core.h:1690)
(inlined l2cap_conn_free net/bluetooth/l2cap_core.c:1856)
(inlined kref_put include/linux/kref.h:65)
(inlined l2cap_conn_put net/bluetooth/l2cap_core.c:1869)
(inlined l2cap_chan_destroy net/bluetooth/l2cap_core.c:494)
(inlined kref_put include/linux/kref.h:65)
l2cap_chan_put+0x1e9/0x260 net/bluetooth/l2cap_core.c:521
l2cap_sock_release+0x198/0x1e0 net/bluetooth/l2cap_sock.c:1533
(inlined __sock_release net/socket.c:735)
sock_release+0x85/0x150 net/socket.c:763
(inlined rfcomm_session_del net/bluetooth/rfcomm/core.c:782)
rfcomm_session_close+0x1f6/0x250 net/bluetooth/rfcomm/core.c:821
(inlined rfcomm_process_rx net/bluetooth/rfcomm/core.c:2086)
(inlined rfcomm_process_sessions net/bluetooth/rfcomm/core.c:2171)
rfcomm_run+0x7481/0x91d0 net/bluetooth/rfcomm/core.c:2261
kthread+0x38b/0x470 kernel/kthread.c:436
ret_from_fork+0x43b/0x8f0 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
-> #0 (rfcomm_mutex){+.+.}-{4:4}:
(inlined check_prev_add kernel/locking/lockdep.c:3209)
(inlined check_prevs_add kernel/locking/lockdep.c:3328)
(inlined validate_chain kernel/locking/lockdep.c:3952)
__lock_acquire+0x17b9/0x2db0 kernel/locking/lockdep.c:5288
lock_acquire+0x115/0x350 kernel/locking/lockdep.c:5942
(inlined __mutex_lock_common kernel/locking/mutex.c:646)
__mutex_lock+0x19c/0x1620 kernel/locking/mutex.c:821
rfcomm_security_cfm+0x37/0x5a0 net/bluetooth/rfcomm/core.c:2279
hci_encrypt_cfm+0x516/0x720 include/net/bluetooth/hci_core.h:2243
hci_encrypt_change_evt+0x6c9/0xde0 net/bluetooth/hci_event.c:3674
(inlined hci_event_func net/bluetooth/hci_event.c:7887)
hci_event_packet+0x7f1/0xe10 net/bluetooth/hci_event.c:7938
hci_rx_work+0x3cc/0x8d0 net/bluetooth/hci_core.c:4087
(inlined process_one_work kernel/workqueue.c:3396)
process_scheduled_works+0xc99/0x1900 kernel/workqueue.c:3479
worker_thread+0x8a2/0xda0 kernel/workqueue.c:3560
kthread+0x38b/0x470 kernel/kthread.c:436
ret_from_fork+0x43b/0x8f0 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
other info that might help us debug this:
Chain exists of:
rfcomm_mutex --> &hdev->lock --> hci_cb_list_lock
Possible unsafe locking scenario:
CPU0 CPU1
---- ----
lock(hci_cb_list_lock);
lock(&hdev->lock);
lock(hci_cb_list_lock);
lock(rfcomm_mutex);
*** DEADLOCK ***
locks held by kworker/u5:2/178: 4, last CPU#0:
(inlined rcu_lock_acquire include/linux/rcupdate.h:309)
(inlined rcu_read_lock include/linux/rcupdate.h:849)
(inlined process_one_work kernel/workqueue.c:3361)
#0: ffff8880271f6940 ((wq_completion)hci1#2){+.+.}-{0:0}, at:
process_scheduled_works+0x94e/0x1900 kernel/workqueue.c:3479
(inlined rcu_lock_acquire include/linux/rcupdate.h:309)
(inlined rcu_read_lock include/linux/rcupdate.h:849)
(inlined process_one_work kernel/workqueue.c:3361)
#1: ffffc90001b87c60 ((work_completion)(&hdev->rx_work)){+.+.}-{0:0},
at: process_scheduled_works+0x94e/0x1900 kernel/workqueue.c:3479
#2: ffff88802deec0b8 (&hdev->lock){+.+.}-{4:4}, at:
hci_encrypt_change_evt+0x84/0xde0 net/bluetooth/hci_event.c:3627
#3: ffffffff8efe4d60 (hci_cb_list_lock){+.+.}-{4:4}, at:
hci_encrypt_cfm+0x4ab/0x720 include/net/bluetooth/hci_core.h:2240
stack backtrace:
CPU: 0 UID: 0 PID: 178 Comm: kworker/u5:2 Not tainted 7.3.0-rc6
#1-NixOS PREEMPT(full)
Workqueue: hci1 hci_rx_work
Call Trace:
<TASK>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
print_circular_bug+0x2e2/0x300 kernel/locking/lockdep.c:2087
check_noncircular+0x12f/0x150 kernel/locking/lockdep.c:2219
(inlined check_prev_add kernel/locking/lockdep.c:3209)
(inlined check_prevs_add kernel/locking/lockdep.c:3328)
(inlined validate_chain kernel/locking/lockdep.c:3952)
__lock_acquire+0x17b9/0x2db0 kernel/locking/lockdep.c:5288
lock_acquire+0x115/0x350 kernel/locking/lockdep.c:5942
(inlined __mutex_lock_common kernel/locking/mutex.c:646)
__mutex_lock+0x19c/0x1620 kernel/locking/mutex.c:821
rfcomm_security_cfm+0x37/0x5a0 net/bluetooth/rfcomm/core.c:2279
hci_encrypt_cfm+0x516/0x720 include/net/bluetooth/hci_core.h:2243
hci_encrypt_change_evt+0x6c9/0xde0 net/bluetooth/hci_event.c:3674
(inlined hci_event_func net/bluetooth/hci_event.c:7887)
hci_event_packet+0x7f1/0xe10 net/bluetooth/hci_event.c:7938
hci_rx_work+0x3cc/0x8d0 net/bluetooth/hci_core.c:4087
(inlined process_one_work kernel/workqueue.c:3396)
process_scheduled_works+0xc99/0x1900 kernel/workqueue.c:3479
worker_thread+0x8a2/0xda0 kernel/workqueue.c:3560
kthread+0x38b/0x470 kernel/kthread.c:436
ret_from_fork+0x43b/0x8f0 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
</TASK>
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-10-11 14:48 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-29 12:22 [PATCH v4] Bluetooth: RFCOMM: connect the session socket without rfcomm_mutex Mikhail Gavrilov
2026-09-29 13:16 ` Mikhail Gavrilov
2026-09-29 14:19 ` Jiaming Zhang
2026-09-29 15:10 ` patchwork-bot+bluetooth
2026-10-11 14:47 Eugene Siegel
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®