From nobody Fri Sep 25 12:33:47 2026 Received: from mail-lf2-f12.google.com (mail-lf2-f12.google.com [74.125.229.204]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 769773ACA6F for ; Sat, 12 Sep 2026 10:03:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.229.204 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789207403; cv=none; b=Ighl6JzK5n1l9YjAgN/gx4Dby1yU90446d7w2XSWOt5NMX9YavZjelZU+BrXORXEjP5X5k463X2edOshZNZKOZ8Tap4A7eff2LntSFdzHvAsifeRSf8Au7B7mX3WBQNbCLsa8y3eKehYHUcbD1nJU7X9JevYhLzAl0LZ/6NkbuQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789207403; c=relaxed/simple; bh=WN5YeKgolI1gaNca7m3o51m/BxZJrR9aLcGuRlXGtj8=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=aQ6aJxfyV2Wo1AmO8qug5qHeYxm1Lkkhfm3mnl5lGsj7F1OdOnqRrQFDMj3kPb+aYOkH3kCjSXduWdmpL/dJl20vIpa2DNFa9YoNtvs7mPeBFJUmqC1JYWEVN02BQUae9JsiSD/Hrnf11VSbPqfglfezqVfQpxwtAEJDUTcU1EQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=EN+yRECQ; arc=none smtp.client-ip=74.125.229.204 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="EN+yRECQ" Received: by mail-lf2-f12.google.com with SMTP id 2adb3069b0e04-5b5e4f16f16so823087e87.0 for ; Sat, 12 Sep 2026 03:03:21 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1789207399; x=1789812199; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to:content-type; bh=jxKIx3r/q2G7x6KZBvEKOw3luI2MoVxuwqdcXemjjfM=; b=EN+yRECQ+V+xn4UHeG9IXu2qqRERbDtS2i0rYA5U5rJseE/cY8yBWggNtxpg9z5V48 KOLKwYu5sQk151VpIPsm9rmjsx+eh6nBfHWkBIKAUpizDbjIVMXrG7yOCl0IqIDHuy7h annCSoCh+w+DRezGjTECynP3lDeMjtZjwDUuvf9izdN1S74dzjhEijOgLNkIWDPSfQg+ laBlUybaLkb/B7o05bZc8Qc/zUwh1l+swXz8JJi2IFkBmMl2z1lxxs6OGNC0amqvDzST CaHaXUhncNWI/UWEZla4DqjicwRJO2E7o/qXJAlEnu/N/81ivxTUa+hdB7jO+UKxyBTR bBIg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1789207399; x=1789812199; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=jxKIx3r/q2G7x6KZBvEKOw3luI2MoVxuwqdcXemjjfM=; b=km+fuTSuB2aF8LLs97eBJ1C3CXuvvP9YHtvplviNVZxzQIMaxxN8FMriFGL8gdzm0S 7Bcdc4N/8Yt+Ls78kFC4aaH34CB7p+nRR2uvcV7y57miYFuhZ8H4urDanKnUFdY69o2K 1rX+AJJUX0vgaqxyZMx3Tv/bCE/j1o0ooyqatrDBpqCxDmDHQiFSQL5aguTBxRUr18Ge eUteXOnJSkVCHbnQF2k5JRzQGEhOH3tMvj9n7d9xB0MHXQIAH7fwvzAPDJQM1ox0PClO IvR+rcB8n1RJh99pbB2IahR2yY391XvVxInNsBDNl5X+9MKFuDTecujSQrR1jvxOJ0yZ W5EA== X-Forwarded-Encrypted: i=1; AKwUvByn4QgeVbLYfpBr+Zy8z8Lc49gNjKp+wD2EVbmDmtDzVBSHKGLElXG2VscVkyo4qjl+3qM/CfaPbLaLRNU=@vger.kernel.org X-Gm-Message-State: AFuF++nM4lUtGbGNDIhLTY3hhEmW3DvhOGn5HwkR+874no8lBcriMDxS HjupiPJCmw+jBIuc09/7tBXyodSdHlPoWDQorKId+zobVqYLQBUVxZdK X-Gm-Gg: AYBFou0Lh2kVnyoultiCOfVemkBPluZKkTdfbi0kWiGn8LYPByfCs1Sf7yO0z+XBncR z0PJvv2UVu4+3dfz0Jrls4Gq8L+1L/fyUpnia1woVoXjmQilcuUpw9cH5PcBFuA23i0zrmhv2x9 0Xfg3XVqP/InSLi/mxk0qgwxOo2HzvsMFsBN1EkYpmiL/d2lQyfEdlySNKtAZ9G6g8d0qBNbkD7 gaSlHCOoBkGo9I/TeGw4feMrmMLk4BE9w4c81uRt9V7fU5QzEIw89EYl6HZ0aQTHCtCCIZvmw4i ScXQ1ZlQLho/pTm0COKY0aSWdF2re2YWxt53LY7iWb3FT7KT3AEuVQwrlcw5W1TGOo2vRlbqLtN 0ujBfj94Jsh0q0/Modc0qUer3MZ52j61P54mlzEZrLrT47CmE7bdZ5oIJtm57AdkyNcV1fOuoqQ 27N68Ivqb/J4W7G9ZK+dScFnGrnaYUroMzzOVPGTyo9qHgOD232h+SJXnE+0vC0wYMunhuqB/2N YtOIPFr3bYcYqdPU2HMwbhrO7AgMjY8Kj4YYvpZgjSBp5TFCdFwY/mJZceH4mwF3VtJtVs= X-Received: by 2002:a05:6512:3e0c:b0:5b1:537b:de9b with SMTP id 2adb3069b0e04-5b8a0325aecmr3236783e87.19.1789207398830; Sat, 12 Sep 2026 03:03:18 -0700 (PDT) Received: from localhost ([188.234.148.119]) by smtp.gmail.com with ESMTPSA id 2adb3069b0e04-5b8a045dd51sm1153427e87.30.2026.09.12.03.03.16 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 12 Sep 2026 03:03:17 -0700 (PDT) From: Mikhail Gavrilov To: marcel@holtmann.org, luiz.dentz@gmail.com Cc: nicoyip.dev@gmail.com, pav@iki.fi, linux-bluetooth@vger.kernel.org, linux-kernel@vger.kernel.org, Mikhail Gavrilov , syzbot+74071deb72339c215b2e@syzkaller.appspotmail.com Subject: [PATCH v3] Bluetooth: RFCOMM: connect the session socket without rfcomm_mutex Date: Sat, 12 Sep 2026 15:03:15 +0500 Message-ID: <20260912100315.151674-1-mikhail.v.gavrilov@gmail.com> X-Mailer: git-send-email 2.55.0 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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. Fixes: 759c185d0bbd ("Bluetooth: RFCOMM: serialize security confirmation ha= ndling") Suggested-by: Pauli Virtanen Reported-by: Pauli Virtanen Closes: https://lore.kernel.org/linux-bluetooth/5e76a95e934e451e7006db28827= c2d64af5a88be.camel@iki.fi/ Reported-by: syzbot+74071deb72339c215b2e@syzkaller.appspotmail.com Closes: https://lore.kernel.org/linux-bluetooth/6a92fadc.08e933ee.dbf97.008= f.GAE@google.com/ Cc: stable@vger.kernel.org Signed-off-by: Mikhail Gavrilov --- 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. v1: https://lore.kernel.org/linux-bluetooth/20260902235132.453044-1-mikhail= .v.gavrilov@gmail.com/ v2: https://lore.kernel.org/linux-bluetooth/20260904012028.77590-1-mikhail.= v.gavrilov@gmail.com/ v3: - fix the lock order on the connect side instead of deferring the security confirmation, as asked for on v2; rfcomm_security_cfm() and krfcommd are left alone, so none of the questions about delayed confirmations apply any more - the queue, its annotations and the flush from v2 are gone Tested on 7.3.0-rc2 with an MT7922 controller (btusb). Without the patch the reproducer below reports the inversion on every run; with it applied it stays quiet and the validator is still armed afterwards (debug_locks: 1). A 10 hour session with BR/EDR headset connects, AVRCP and SCO traffic produced no lockdep report either. An outgoing connect towards a connected headset is answered in 29 ms with ECONNREFUSED - the session is established over the existing ACL link and the peer rejects the channel - which is the case where the L2CAP connect can complete before the session reaches the list. Towards an idle device the same connect fails with EHOSTDOWN after the page timeout. The connect() side used for the reproducer, so that it does not depend on which end sets up the HFP session: #include #include #include #include #define BTPROTO_RFCOMM 3 struct sockaddr_rc { unsigned short rc_family; uint8_t rc_bdaddr[6]; /* little endian */ uint8_t rc_channel; }; int main(void) { struct sockaddr_rc addr =3D { .rc_family =3D AF_BLUETOOTH, .rc_channel =3D 1 }; int fd =3D socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); memcpy(addr.rc_bdaddr, "\x55\x44\x33\x22\x11\x00", 6); connect(fd, (struct sockaddr *)&addr, sizeof(addr)); close(fd); return 0; } net/bluetooth/rfcomm/core.c | 114 ++++++++++++++++++++++++------------ 1 file changed, 76 insertions(+), 38 deletions(-) diff --git a/net/bluetooth/rfcomm/core.c b/net/bluetooth/rfcomm/core.c index f7463f092283..227ccc7da848 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= ); =20 static void rfcomm_process_connect(struct rfcomm_session *s); =20 -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); =20 @@ -365,28 +364,17 @@ static int rfcomm_check_channel(u8 channel) return channel < 1 || channel > 30; } =20 -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 =3D 0; u8 dlci; =20 - 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); =20 if (d->state !=3D BT_OPEN && d->state !=3D BT_CLOSED) return 0; =20 - s =3D rfcomm_session_get(src, dst); - if (!s) { - s =3D rfcomm_session_create(src, dst, d->sec_level, &err); - if (!s) - return err; - } - dlci =3D __dlci(__session_dir(s), channel); =20 /* Check if DLCI already exists */ @@ -421,14 +409,72 @@ static int __rfcomm_dlc_open(struct rfcomm_dlc *d, bd= addr_t *src, bdaddr_t *dst, =20 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; =20 rfcomm_lock(); =20 - r =3D __rfcomm_dlc_open(d, src, dst, channel); + /* 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 !=3D BT_OPEN && d->state !=3D BT_CLOSED) { + rfcomm_unlock(); + return 0; + } =20 + s =3D rfcomm_session_get(src, dst); + if (s) { + err =3D __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 =3D rfcomm_session_connect(src, dst, d->sec_level, &err); + if (!sock) + return err; + + rfcomm_lock(); + + /* Another opener may have added a session for the same pair in the + * meantime; that one is used and this socket is dropped. + */ + s =3D rfcomm_session_get(src, dst); + if (!s) { + s =3D rfcomm_session_add(sock, BT_BOUND); + if (s) { + s->initiator =3D 1; + sock =3D NULL; + } + } + + err =3D 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; } =20 static void __rfcomm_dlc_disconn(struct rfcomm_dlc *d) @@ -757,12 +803,12 @@ static struct rfcomm_session *rfcomm_session_close(st= ruct rfcomm_session *s, return rfcomm_session_del(s); } =20 -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 =3D NULL; struct sockaddr_l2 addr; struct socket *sock; struct sock *sk; @@ -792,24 +838,16 @@ static struct rfcomm_session *rfcomm_session_create(b= daddr_t *src, l2cap_pi(sk)->chan->mode =3D L2CAP_MODE_ERTM; release_sock(sk); =20 - s =3D rfcomm_session_add(sock, BT_BOUND); - if (!s) { - *err =3D -ENOMEM; - goto failed; - } - - s->initiator =3D 1; - bacpy(&addr.l2_bdaddr, dst); addr.l2_family =3D AF_BLUETOOTH; addr.l2_psm =3D cpu_to_le16(L2CAP_PSM_RFCOMM); addr.l2_cid =3D 0; addr.l2_bdaddr_type =3D BDADDR_BREDR; *err =3D kernel_connect(sock, (struct sockaddr_unsized *)&addr, sizeof(ad= dr), O_NONBLOCK); - if (*err =3D=3D 0 || *err =3D=3D -EINPROGRESS) - return s; + if (*err && *err !=3D -EINPROGRESS) + goto failed; =20 - return rfcomm_session_del(s); + return sock; =20 failed: sock_release(sock); --=20 2.55.0