From: Pauli Virtanen <pav@iki.fi>
To: Mikhail Gavrilov <mikhail.v.gavrilov@gmail.com>,
marcel@holtmann.org, luiz.dentz@gmail.com
Cc: nicoyip.dev@gmail.com, linux-bluetooth@vger.kernel.org,
linux-kernel@vger.kernel.org,
syzbot+74071deb72339c215b2e@syzkaller.appspotmail.com
Subject: Re: [PATCH] Bluetooth: RFCOMM: defer security confirmation to krfcommd
Date: Thu, 03 Sep 2026 19:47:26 +0300 [thread overview]
Message-ID: <947b663be7dded8a00aad6a277bb82eb8a9069be.camel@iki.fi> (raw)
In-Reply-To: <20260902235132.453044-1-mikhail.v.gavrilov@gmail.com>
Hi,
to, 2026-09-03 kello 04:51 +0500, Mikhail Gavrilov kirjoitti:
> 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.
>
> The callback does not have to run in the HCI event context at all: it
> only updates DLC flags and timers that krfcommd consumes in
> rfcomm_process_dlcs(), and it already ends with rfcomm_schedule(). So
> queue the confirmation instead of taking rfcomm_mutex from the HCI
> event path, and let krfcommd apply it under rfcomm_mutex on its next
> pass, ahead of session processing. The queued entry carries the local
> address and a reference on the connection, so the session lookup and
> hci_conn_check_secure() stay valid without hdev->lock. A confirmation
> that cannot be allocated is dropped and the DLC closes on its auth
> timeout.
Sashiko review comment that security_cfm() cleanup should be run in
rfcomm_init() after hci_unregister_cb(), appears correct.
AFAICS, none of the callsites of hci_auth_cfm(), which call the
security_cfm, require that it is synchronous under the lock.
However, rfcomm_session_get() could return a different session if
processing is delayed. Is this a concern? ABA issue?
This patch introduces data race in read of conn->cfm->sec_level,
probably benign, but these are not marked with READ_ONCE/WRITE_ONCE.
I'd maybe take hdev_lock in rfcomm_process_security_cfm instead. This
requires struct hci_dev *hdev; added in rfcomm_sec_cfm and hci_dev
get/put, since hci_conn_get() does not guarantee hci_conn::hdev is
valid pointer.
I'd maybe also add Documentation/dev-tools/context-analysis.rst
annotations while at it, unless it requires extensive changes.
> Fixes: 759c185d0bbd ("Bluetooth: RFCOMM: serialize security confirmation handling")
> 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/
> 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.
>
> Tested on 7.3.0-rc1 with an MTK MT7921 controller (btusb) and a JBL
> Tour Pro 3 headset. Without this patch the steps above report the
> inversion on every run. With it applied the reproducer leaves the
> validator armed and silent (debug_locks: 1), and a 5.5 hour session
> with four headset connects, HFP/SCO audio and AVRCP produced no
> lockdep report either.
>
> The connect() side used for testing, so that it does not depend on which
> end sets up the HFP session:
>
> #include <stdint.h>
> #include <string.h>
> #include <unistd.h>
> #include <sys/socket.h>
>
> #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 = { .rc_family = AF_BLUETOOTH,
> .rc_channel = 1 };
> int fd = 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 | 139 ++++++++++++++++++++++++++----------
> 1 file changed, 100 insertions(+), 39 deletions(-)
>
> diff --git a/net/bluetooth/rfcomm/core.c b/net/bluetooth/rfcomm/core.c
> index f7463f092283..728a6bd2986b 100644
> --- a/net/bluetooth/rfcomm/core.c
> +++ b/net/bluetooth/rfcomm/core.c
> @@ -49,6 +49,18 @@ static DEFINE_MUTEX(rfcomm_mutex);
>
> static LIST_HEAD(session_list);
>
> +/* Security confirmations handed over from the HCI event handler to krfcommd */
> +struct rfcomm_sec_cfm {
> + struct list_head list;
> + struct hci_conn *conn;
> + bdaddr_t src;
> + u8 status;
> + u8 encrypt;
> +};
> +
> +static LIST_HEAD(security_cfm_list);
> +static DEFINE_SPINLOCK(security_cfm_lock);
Context analysis annotations would be useful here:
static DEFINE_SPINLOCK(security_cfm_lock);
static __guarded_by(&security_cfm_lock) LIST_HEAD(security_cfm_list);
struct rfcomm_sec_cfm {
struct list_head list __guarded_by(&security_cfm_lock);
struct hci_conn *conn;
bdaddr_t src;
u8 status;
u8 encrypt;
};
Static checker can be run with Clang 23,
make LLVM=1 net/bluetooth/
> static int rfcomm_send_frame(struct rfcomm_session *s, u8 *data, int len);
> static int rfcomm_send_sabm(struct rfcomm_session *s, u8 dlci);
> static int rfcomm_send_disc(struct rfcomm_session *s, u8 dlci);
> @@ -2122,6 +2134,73 @@ static void rfcomm_process_sessions(void)
> rfcomm_unlock();
> }
>
> +/* Must be called with rfcomm_mutex held */
> +static void __rfcomm_security_cfm(struct rfcomm_sec_cfm *cfm)
__must_hold(&rfcomm_mutex) annotation is better than comment, although
would be needed also in the caller.
> +{
> + struct rfcomm_session *s;
> + struct rfcomm_dlc *d, *n;
> +
> + s = rfcomm_session_get(&cfm->src, &cfm->conn->dst);
> + if (!s)
> + return;
> +
> + list_for_each_entry_safe(d, n, &s->dlcs, list) {
> + if (test_and_clear_bit(RFCOMM_SEC_PENDING, &d->flags)) {
> + rfcomm_dlc_clear_timer(d);
> + if (cfm->status || cfm->encrypt == 0x00) {
> + set_bit(RFCOMM_ENC_DROP, &d->flags);
> + continue;
> + }
> + }
> +
> + if (d->state == BT_CONNECTED && !cfm->status &&
> + cfm->encrypt == 0x00) {
> + if (d->sec_level == BT_SECURITY_MEDIUM) {
> + set_bit(RFCOMM_SEC_PENDING, &d->flags);
> + rfcomm_dlc_set_timer(d, RFCOMM_AUTH_TIMEOUT);
> + continue;
> + } else if (d->sec_level == BT_SECURITY_HIGH ||
> + d->sec_level == BT_SECURITY_FIPS) {
> + set_bit(RFCOMM_ENC_DROP, &d->flags);
> + continue;
> + }
> + }
> +
> + if (!test_and_clear_bit(RFCOMM_AUTH_PENDING, &d->flags))
> + continue;
> +
> + if (!cfm->status && hci_conn_check_secure(cfm->conn,
> + d->sec_level))
> + set_bit(RFCOMM_AUTH_ACCEPT, &d->flags);
> + else
> + set_bit(RFCOMM_AUTH_REJECT, &d->flags);
> + }
> +}
> +
> +static void rfcomm_process_security_cfm(void)
> +{
> + struct rfcomm_sec_cfm *cfm, *n;
> + LIST_HEAD(cfm_list);
> +
> + spin_lock(&security_cfm_lock);
> + list_splice_init(&security_cfm_list, &cfm_list);
> + spin_unlock(&security_cfm_lock);
> +
> + if (list_empty(&cfm_list))
> + return;
> +
> + rfcomm_lock();
> +
> + list_for_each_entry_safe(cfm, n, &cfm_list, list) {
> + __rfcomm_security_cfm(cfm);
> + list_del(&cfm->list);
> + hci_conn_put(cfm->conn);
> + kfree(cfm);
> + }
> +
> + rfcomm_unlock();
> +}
> +
> static int rfcomm_add_listener(bdaddr_t *ba)
> {
> struct sockaddr_l2 addr;
> @@ -2201,12 +2280,18 @@ static int rfcomm_run(void *unused)
> while (!kthread_should_stop()) {
>
> /* Process stuff */
> + rfcomm_process_security_cfm();
> rfcomm_process_sessions();
>
> wait_woken(&wait, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
> }
> remove_wait_queue(&rfcomm_wq, &wait);
>
> + /* rfcomm_exit() unregisters the HCI callback before stopping this
> + * thread, so no further confirmation can be queued here.
> + */
> + rfcomm_process_security_cfm();
> +
> rfcomm_kill_listener();
>
> return 0;
> @@ -2214,50 +2299,26 @@ static int rfcomm_run(void *unused)
>
> static void rfcomm_security_cfm(struct hci_conn *conn, u8 status, u8 encrypt)
> {
> - struct rfcomm_session *s;
> - struct rfcomm_dlc *d, *n;
> + struct rfcomm_sec_cfm *cfm;
>
> BT_DBG("conn %p status 0x%02x encrypt 0x%02x", conn, status, encrypt);
>
> - rfcomm_lock();
> -
> - s = rfcomm_session_get(&conn->hdev->bdaddr, &conn->dst);
> - if (!s) {
> - rfcomm_unlock();
> + cfm = kmalloc_obj(*cfm);
> + if (!cfm)
> return;
> - }
> -
> - list_for_each_entry_safe(d, n, &s->dlcs, list) {
> - if (test_and_clear_bit(RFCOMM_SEC_PENDING, &d->flags)) {
> - rfcomm_dlc_clear_timer(d);
> - if (status || encrypt == 0x00) {
> - set_bit(RFCOMM_ENC_DROP, &d->flags);
> - continue;
> - }
> - }
>
> - if (d->state == BT_CONNECTED && !status && encrypt == 0x00) {
> - if (d->sec_level == BT_SECURITY_MEDIUM) {
> - set_bit(RFCOMM_SEC_PENDING, &d->flags);
> - rfcomm_dlc_set_timer(d, RFCOMM_AUTH_TIMEOUT);
> - continue;
> - } else if (d->sec_level == BT_SECURITY_HIGH ||
> - d->sec_level == BT_SECURITY_FIPS) {
> - set_bit(RFCOMM_ENC_DROP, &d->flags);
> - continue;
> - }
> - }
> -
> - if (!test_and_clear_bit(RFCOMM_AUTH_PENDING, &d->flags))
> - continue;
> -
> - if (!status && hci_conn_check_secure(conn, d->sec_level))
> - set_bit(RFCOMM_AUTH_ACCEPT, &d->flags);
> - else
> - set_bit(RFCOMM_AUTH_REJECT, &d->flags);
> - }
> -
> - rfcomm_unlock();
> + /* The connection is pinned for hci_conn_check_secure(), but it drops
> + * its reference on hdev once it is deleted, so take a copy of the
> + * local address needed for the session lookup.
> + */
> + cfm->conn = hci_conn_get(conn);
> + bacpy(&cfm->src, &conn->hdev->bdaddr);
> + cfm->status = status;
> + cfm->encrypt = encrypt;
> +
> + spin_lock(&security_cfm_lock);
> + list_add_tail(&cfm->list, &security_cfm_list);
> + spin_unlock(&security_cfm_lock);
>
> rfcomm_schedule();
> }
--
Pauli Virtanen
next prev parent reply other threads:[~2026-09-03 16:47 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-02 23:51 [PATCH] Bluetooth: RFCOMM: defer security confirmation to krfcommd Mikhail Gavrilov
2026-09-03 1:55 ` bluez.test.bot
2026-09-03 16:47 ` Pauli Virtanen [this message]
2026-09-04 0:55 ` [PATCH] " Mikhail Gavrilov
2026-09-04 1:20 ` [PATCH v2] " Mikhail Gavrilov
2026-09-04 5:40 ` [v2] " bluez.test.bot
2026-09-05 11:54 ` [PATCH v2] " Pauli Virtanen
2026-09-05 14:49 ` mikhail.v.gavrilov
2026-09-11 19:32 ` Luiz Augusto von Dentz
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=947b663be7dded8a00aad6a277bb82eb8a9069be.camel@iki.fi \
--to=pav@iki.fi \
--cc=linux-bluetooth@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=luiz.dentz@gmail.com \
--cc=marcel@holtmann.org \
--cc=mikhail.v.gavrilov@gmail.com \
--cc=nicoyip.dev@gmail.com \
--cc=syzbot+74071deb72339c215b2e@syzkaller.appspotmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.