mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
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

  reply	other threads:[~2026-09-03 16:47 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-02 23:51 Mikhail Gavrilov
2026-09-03 16:47 ` Pauli Virtanen [this message]
2026-09-04  0:55   ` Mikhail Gavrilov
2026-09-04  1:20 ` [PATCH v2] " Mikhail Gavrilov
2026-09-05 11:54   ` Pauli Virtanen
2026-09-05 14:49     ` mikhail.v.gavrilov

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 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®