Linux bluetooth development
 help / color / mirror / Atom feed
* RE: [1/4] Bluetooth: hci_sync: pin conn across hci_le_create_conn_sync
From: bluez.test.bot @ 2026-05-11 18:05 UTC (permalink / raw)
  To: linux-bluetooth, michael.bommarito
In-Reply-To: <490e228dd02983fb1530fb114d4174148f810261.1778506829.git.michael.bommarito@gmail.com>

[-- Attachment #1: Type: text/plain, Size: 3885 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/project/bluetooth/list/?series=1092854

---Test result---

Test Summary:
CheckPatch                    PASS      2.15 seconds
GitLint                       FAIL      0.80 seconds
SubjectPrefix                 PASS      0.25 seconds
BuildKernel                   PASS      25.08 seconds
CheckAllWarning               PASS      27.34 seconds
CheckSparse                   PASS      26.59 seconds
BuildKernel32                 PASS      24.63 seconds
TestRunnerSetup               PASS      532.72 seconds
TestRunner_l2cap-tester       PASS      379.10 seconds
TestRunner_iso-tester         PASS      601.56 seconds
TestRunner_bnep-tester        PASS      19.56 seconds
TestRunner_mgmt-tester        PASS      2024.23 seconds
TestRunner_rfcomm-tester      PASS      63.47 seconds
TestRunner_sco-tester         PASS      141.37 seconds
TestRunner_ioctl-tester       PASS      133.62 seconds
TestRunner_mesh-tester        PASS      60.51 seconds
TestRunner_smp-tester         PASS      18.60 seconds
TestRunner_userchan-tester    PASS      20.23 seconds
TestRunner_6lowpan-tester     PASS      51.88 seconds
IncrementalBuild              PASS      32.90 seconds

Details
##############################
Test: GitLint - FAIL
Desc: Run gitlint
Output:
[1/4] Bluetooth: hci_sync: pin conn across hci_le_create_conn_sync

WARNING: I3 - ignore-body-lines: gitlint will be switching from using Python regex 'match' (match beginning) to 'search' (match anywhere) semantics. Please review your ignore-body-lines.regex option accordingly. To remove this warning, set general.regex-style-search=True. More details: https://jorisroovers.github.io/gitlint/configuration/#regex-style-search
41: B1 Line exceeds max length (107>80): "https://lore.kernel.org/linux-bluetooth/e18591f264c50e15917cb8b9e5f9798d9880979d.1762100290.git.pav@iki.fi/"
[2/4] Bluetooth: hci_sync: pin conn across hci_le_pa_create_sync

WARNING: I3 - ignore-body-lines: gitlint will be switching from using Python regex 'match' (match beginning) to 'search' (match anywhere) semantics. Please review your ignore-body-lines.regex option accordingly. To remove this warning, set general.regex-style-search=True. More details: https://jorisroovers.github.io/gitlint/configuration/#regex-style-search
25: B1 Line exceeds max length (108>80): "https://lore.kernel.org/linux-bluetooth/e18591f264c50e15917cb8b9e5f9798d9880979d.1762100290.git.pav@iki.fi/."
[3/4] Bluetooth: hci_sync: pin conn across hci_le_big_create_sync

WARNING: I3 - ignore-body-lines: gitlint will be switching from using Python regex 'match' (match beginning) to 'search' (match anywhere) semantics. Please review your ignore-body-lines.regex option accordingly. To remove this warning, set general.regex-style-search=True. More details: https://jorisroovers.github.io/gitlint/configuration/#regex-style-search
25: B1 Line exceeds max length (108>80): "https://lore.kernel.org/linux-bluetooth/e18591f264c50e15917cb8b9e5f9798d9880979d.1762100290.git.pav@iki.fi/."
[4/4] Bluetooth: hci_sync: pin conn across hci_acl_create_conn_sync

WARNING: I3 - ignore-body-lines: gitlint will be switching from using Python regex 'match' (match beginning) to 'search' (match anywhere) semantics. Please review your ignore-body-lines.regex option accordingly. To remove this warning, set general.regex-style-search=True. More details: https://jorisroovers.github.io/gitlint/configuration/#regex-style-search
29: B1 Line exceeds max length (108>80): "https://lore.kernel.org/linux-bluetooth/e18591f264c50e15917cb8b9e5f9798d9880979d.1762100290.git.pav@iki.fi/."


https://github.com/bluez/bluetooth-next/pull/169

---
Regards,
Linux Bluetooth


^ permalink raw reply

* Re: [PATCH 1/4] Bluetooth: hci_sync: pin conn across hci_le_create_conn_sync
From: Luiz Augusto von Dentz @ 2026-05-11 17:35 UTC (permalink / raw)
  To: Michael Bommarito
  Cc: Marcel Holtmann, linux-bluetooth, linux-kernel, Mat Martineau,
	netdev, stable, Pauli Virtanen, Aaron Esau
In-Reply-To: <CAJJ9bXxdm6LRW-5a4a1eDyZSQjNkJ4PNF+aERHkHe9EMQFX8oA@mail.gmail.com>

Hi Michael,

On Mon, May 11, 2026 at 1:01 PM Michael Bommarito
<michael.bommarito@gmail.com> wrote:
>
> On Mon, May 11, 2026 at 10:53 AM Luiz Augusto von Dentz
> <luiz.dentz@gmail.com> wrote:
> > Id suggest we dropped the once at the end so just hci_cmd_sync_queue_conn.
> >
> > > +                                       hci_cmd_sync_work_func_t func,
> > > +                                       struct hci_conn *conn,
> > > +                                       hci_cmd_sync_work_destroy_t destroy)
> > > +{
> > > +       int err;
> > > +
> > > +       err = hci_cmd_sync_queue_once(hdev, func, hci_conn_get(conn), destroy);
> > > +       if (err)
> > > +               hci_conn_put(conn);
> > > +
> > > +       return err;
> >
> > Then we incorporate return (err == -EEXIST) ? 0 : err; logic above, so
> > I don't think any caller should require queuing multiple procedures
> > for the same conn.
>
> OK, good call.  I checked the other 10 callers for
> hci_cmd_sync_queue_once and there isn't any variation today, so that
> seems safe.
>
> Do you want me to wait another 24-48 for others to weigh in or ship v2 now?

Just create a v2, anyone with comments can add them there as well.

-- 
Luiz Augusto von Dentz

^ permalink raw reply

* RE: Add helper for "cleanup" variable attribute
From: bluez.test.bot @ 2026-05-11 17:27 UTC (permalink / raw)
  To: linux-bluetooth, hadess
In-Reply-To: <20260511132131.1283892-2-hadess@hadess.net>

[-- Attachment #1: Type: text/plain, Size: 5159 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/project/bluetooth/list/?series=1092801

---Test result---

Test Summary:
CheckPatch                    FAIL      2.71 seconds
GitLint                       PASS      1.76 seconds
BuildEll                      PASS      20.40 seconds
BluezMake                     PASS      666.47 seconds
MakeCheck                     PASS      18.72 seconds
MakeDistcheck                 PASS      245.36 seconds
CheckValgrind                 PASS      291.26 seconds
CheckSmatch                   WARNING   351.83 seconds
bluezmakeextell               PASS      186.73 seconds
IncrementalBuild              PASS      1330.28 seconds
ScanBuild                     PASS      1038.55 seconds

Details
##############################
Test: CheckPatch - FAIL
Desc: Run checkpatch.pl script
Output:
[BlueZ,v3,2/6] shared/util: Add helper for "cleanup" variable attribute
ERROR:POINTER_LOCATION: "foo * bar" should be "foo *bar"
#106: FILE: src/shared/util.h:103:
+static inline void * _steal_(void *p)

WARNING:LONG_LINE: line length of 104 exceeds 80 columns
#132: FILE: src/shared/util.h:129:
+#define CLEANUP_FREEFUNC(type, func) static inline void cleanup_##type (type **_ptr) { (func) (*_ptr); }

WARNING:SPACING: space prohibited between function name and open parenthesis '('
#132: FILE: src/shared/util.h:129:
+#define CLEANUP_FREEFUNC(type, func) static inline void cleanup_##type (type **_ptr) { (func) (*_ptr); }

ERROR:SPACING: need consistent spacing around '*' (ctx:WxO)
#132: FILE: src/shared/util.h:129:
+#define CLEANUP_FREEFUNC(type, func) static inline void cleanup_##type (type **_ptr) { (func) (*_ptr); }
                                                                              ^

WARNING:LINE_SPACING: Missing a blank line after declarations
#184: FILE: unit/test-util.c:46:
+		_cleanup_free_ uint8_t *data = NULL;
+		data = malloc0(128 * 2);

/github/workspace/src/patch/14565706.patch total: 2 errors, 3 warnings, 140 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.

/github/workspace/src/patch/14565706.patch has style problems, please review.

NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO

NOTE: If any of the errors are false positives, please report
      them to the maintainer, see CHECKPATCH in MAINTAINERS.


[BlueZ,v3,4/6] main: Use _cleanup_() to simplify configuration parsing
ERROR:SPACING: need consistent spacing around '*' (ctx:WxV)
#84: FILE: src/main.c:258:
+	_cleanup_type_(GError) GError *err = NULL;
 	                              ^

ERROR:SPACING: need consistent spacing around '*' (ctx:WxV)
#108: FILE: src/main.c:440:
+	_cleanup_type_(GError) GError *err = NULL;
 	                              ^

ERROR:SPACING: need consistent spacing around '*' (ctx:WxV)
#204: FILE: src/main.c:888:
+	_cleanup_type_(GError) GError *err = NULL;
 	                              ^

ERROR:SPACING: need consistent spacing around '*' (ctx:WxV)
#409: FILE: src/main.c:1207:
+	_cleanup_type_(GError) GError *err = NULL;
 	                              ^

ERROR:SPACING: need consistent spacing around '*' (ctx:WxV)
#501: FILE: src/main.c:1570:
+	_cleanup_type_(GError) GError *err = NULL;
 	                              ^

/github/workspace/src/patch/14565665.patch total: 5 errors, 0 warnings, 413 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.

/github/workspace/src/patch/14565665.patch has style problems, please review.

NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO

NOTE: If any of the errors are false positives, please report
      them to the maintainer, see CHECKPATCH in MAINTAINERS.


##############################
Test: CheckSmatch - WARNING
Desc: Run smatch tool with source
Output:
src/main.c: note: in included file (through src/device.h):src/shared/gatt-server.c:271:25: warning: Variable length array is used.src/shared/gatt-server.c:614:25: warning: Variable length array is used.src/shared/gatt-server.c:712:25: warning: Variable length array is used.src/shared/gatt-server.c:271:25: warning: Variable length array is used.src/shared/gatt-server.c:614:25: warning: Variable length array is used.src/shared/gatt-server.c:712:25: warning: Variable length array is used.src/shared/gatt-server.c:271:25: warning: Variable length array is used.src/shared/gatt-server.c:614:25: warning: Variable length array is used.src/shared/gatt-server.c:712:25: warning: Variable length array is used.src/main.c: note: in included file (through src/device.h):


https://github.com/bluez/bluez/pull/2118

---
Regards,
Linux Bluetooth


^ permalink raw reply

* [PATCH v3] Bluetooth: 6lowpan: Fix peer and channel lifetime during teardown
From: Zhang Cen @ 2026-05-11 17:23 UTC (permalink / raw)
  To: Marcel Holtmann, Luiz Augusto von Dentz
  Cc: linux-bluetooth, linux-kernel, Hillf Danton, zerocling0077,
	2045gemini

A 6LoWPAN peer keeps a protocol-owned L2CAP channel reference in
peer->chan. chan_close_cb() removes the peer from the RCU list when the
channel is closed, but the old code also dropped that channel reference
before the peer object stopped being observable by in-flight RCU readers.

The buggy scenario involves two paths, with each column showing the order
within that path:

  close path:                            lookup/transmit path:
  l2cap_conn_del() or another            setup_header(), send_mcast_pkt(),
  close path holds a temporary           bt_6lowpan_disconnect(), or
  channel reference                      chan_recv_cb() observes a peer

  l2cap_chan_del() drops the             the reader leaves the RCU read
  connection-owned channel reference     side critical section, or keeps
                                         using peer->chan inside it

  chan_close_cb() removes the peer       the reader reads channel fields,
  from the peer list                     locks the channel, or sends
                                         through peer->chan

  the old chan_close_cb() dropped
  the peer-owned channel reference

  the close caller releases its
  temporary channel reference

If the close path consumes the peer-owned channel reference before a
reader finishes with peer->chan, the reader can use a freed l2cap_chan.
The lookup helpers that returned raw peer pointers after rcu_read_unlock()
also let callers outlive the peer object's kfree_rcu() lifetime.

Give lowpan_peer its own refcount and make RCU lookups acquire a peer
reference before returning a peer to code that runs outside the read-side
critical section. Keep the peer-owned L2CAP channel reference attached to
the peer until the last peer reference is dropped.

The last peer put can happen from an RCU callback or from the network
transmit path, so do not drop the final L2CAP channel reference directly
from lowpan_peer_put(). Queue final peer destruction to a dedicated
workqueue and release the channel and module references there. Store the
peer, rather than a temporary channel reference, in the skb control block
for unicast transmit, and put the peer after the send path is done.
Multicast continues to use peer->chan only while it is inside the RCU
read-side critical section.

Wait for queued RCU callbacks and peer-destroy work during module exit so
asynchronous peer destruction cannot outlive the module text.

Signed-off-by: Zhang Cen <rollkingzzc@gmail.com>
---
 net/bluetooth/6lowpan.c | 128 ++++++++++++++++++++++++++++++++--------
 1 file changed, 104 insertions(+), 24 deletions(-)

diff --git a/net/bluetooth/6lowpan.c b/net/bluetooth/6lowpan.c
index 23a229ab6..7a1b0d7b0 100644
--- a/net/bluetooth/6lowpan.c
+++ b/net/bluetooth/6lowpan.c
@@ -9,6 +9,8 @@
 #include <linux/etherdevice.h>
 #include <linux/module.h>
 #include <linux/debugfs.h>
+#include <linux/refcount.h>
+#include <linux/workqueue.h>
 
 #include <net/ipv6.h>
 #include <net/ip6_route.h>
@@ -29,10 +31,12 @@ static struct dentry *lowpan_control_debugfs;
 
 #define IFACE_NAME_TEMPLATE "bt%d"
 
+struct lowpan_peer;
+
 struct skb_cb {
 	struct in6_addr addr;
 	struct in6_addr gw;
-	struct l2cap_chan *chan;
+	struct lowpan_peer *peer;
 };
 #define lowpan_cb(skb) ((struct skb_cb *)((skb)->cb))
 
@@ -52,6 +56,7 @@ static bool enable_6lowpan;
  */
 static struct l2cap_chan *listen_chan;
 static DEFINE_MUTEX(set_lock);
+static struct workqueue_struct *lowpan_peer_wq;
 
 enum {
 	LOWPAN_PEER_CLOSING,
@@ -61,6 +66,8 @@ enum {
 struct lowpan_peer {
 	struct list_head list;
 	struct rcu_head rcu;
+	struct work_struct destroy_work;
+	refcount_t refcnt;
 	struct l2cap_chan *chan;
 
 	/* peer addresses in various formats */
@@ -95,13 +102,39 @@ static inline void peer_add(struct lowpan_btle_dev *dev,
 	atomic_inc(&dev->peer_count);
 }
 
+static inline bool lowpan_peer_get_unless_zero(struct lowpan_peer *peer)
+{
+	return refcount_inc_not_zero(&peer->refcnt);
+}
+
+static void lowpan_peer_destroy(struct work_struct *work)
+{
+	struct lowpan_peer *peer = container_of(work, struct lowpan_peer,
+						destroy_work);
+
+	l2cap_chan_put(peer->chan);
+	kfree(peer);
+	module_put(THIS_MODULE);
+}
+
+static void lowpan_peer_put(struct lowpan_peer *peer)
+{
+	if (refcount_dec_and_test(&peer->refcnt))
+		queue_work(lowpan_peer_wq, &peer->destroy_work);
+}
+
+static void lowpan_peer_put_rcu(struct rcu_head *rcu)
+{
+	struct lowpan_peer *peer = container_of(rcu, struct lowpan_peer, rcu);
+
+	lowpan_peer_put(peer);
+}
+
 static inline bool peer_del(struct lowpan_btle_dev *dev,
 			    struct lowpan_peer *peer)
 {
 	list_del_rcu(&peer->list);
-	kfree_rcu(peer, rcu);
-
-	module_put(THIS_MODULE);
+	call_rcu(&peer->rcu, lowpan_peer_put_rcu);
 
 	if (atomic_dec_and_test(&dev->peer_count)) {
 		BT_DBG("last peer");
@@ -179,7 +212,8 @@ static inline struct lowpan_peer *peer_lookup_dst(struct lowpan_btle_dev *dev,
 		       &peer->chan->dst, peer->chan->dst_type,
 		       &peer->peer_addr);
 
-		if (!ipv6_addr_cmp(&peer->peer_addr, nexthop)) {
+		if (!ipv6_addr_cmp(&peer->peer_addr, nexthop) &&
+		    lowpan_peer_get_unless_zero(peer)) {
 			rcu_read_unlock();
 			return peer;
 		}
@@ -189,7 +223,8 @@ static inline struct lowpan_peer *peer_lookup_dst(struct lowpan_btle_dev *dev,
 	neigh = __ipv6_neigh_lookup(dev->netdev, nexthop);
 	if (neigh) {
 		list_for_each_entry_rcu(peer, &dev->peers, list) {
-			if (!memcmp(neigh->ha, peer->lladdr, ETH_ALEN)) {
+			if (!memcmp(neigh->ha, peer->lladdr, ETH_ALEN) &&
+			    lowpan_peer_get_unless_zero(peer)) {
 				neigh_release(neigh);
 				rcu_read_unlock();
 				return peer;
@@ -212,8 +247,9 @@ static struct lowpan_peer *lookup_peer(struct l2cap_conn *conn)
 
 	list_for_each_entry_rcu(entry, &bt_6lowpan_devices, list) {
 		peer = __peer_lookup_conn(entry, conn);
-		if (peer)
+		if (peer && lowpan_peer_get_unless_zero(peer))
 			break;
+		peer = NULL;
 	}
 
 	rcu_read_unlock();
@@ -361,8 +397,10 @@ static int chan_recv_cb(struct l2cap_chan *chan, struct sk_buff *skb)
 		return -ENOENT;
 
 	dev = lookup_dev(chan->conn);
-	if (!dev || !dev->netdev)
+	if (!dev || !dev->netdev) {
+		lowpan_peer_put(peer);
 		return -ENOENT;
+	}
 
 	err = recv_pkt(skb, dev->netdev, peer);
 	if (err) {
@@ -370,6 +408,8 @@ static int chan_recv_cb(struct l2cap_chan *chan, struct sk_buff *skb)
 		err = -EAGAIN;
 	}
 
+	lowpan_peer_put(peer);
+
 	return err;
 }
 
@@ -380,6 +420,8 @@ static int setup_header(struct sk_buff *skb, struct net_device *netdev,
 	struct ipv6hdr *hdr;
 	struct lowpan_btle_dev *dev;
 	struct lowpan_peer *peer;
+	struct l2cap_chan *chan;
+	u8 peer_lladdr[ETH_ALEN];
 	u8 *daddr;
 	int err, status = 0;
 
@@ -388,9 +430,9 @@ static int setup_header(struct sk_buff *skb, struct net_device *netdev,
 	dev = lowpan_btle_dev(netdev);
 
 	memcpy(&ipv6_daddr, &hdr->daddr, sizeof(ipv6_daddr));
+	lowpan_cb(skb)->peer = NULL;
 
 	if (ipv6_addr_is_multicast(&ipv6_daddr)) {
-		lowpan_cb(skb)->chan = NULL;
 		daddr = NULL;
 	} else {
 		BT_DBG("dest IP %pI6c", &ipv6_daddr);
@@ -406,10 +448,13 @@ static int setup_header(struct sk_buff *skb, struct net_device *netdev,
 			return -ENOENT;
 		}
 
-		daddr = peer->lladdr;
-		*peer_addr = peer->chan->dst;
-		*peer_addr_type = peer->chan->dst_type;
-		lowpan_cb(skb)->chan = peer->chan;
+		chan = peer->chan;
+		memcpy(peer_lladdr, peer->lladdr, ETH_ALEN);
+		*peer_addr = chan->dst;
+		*peer_addr_type = chan->dst_type;
+		lowpan_cb(skb)->peer = peer;
+
+		daddr = peer_lladdr;
 
 		status = 1;
 	}
@@ -417,8 +462,13 @@ static int setup_header(struct sk_buff *skb, struct net_device *netdev,
 	lowpan_header_compress(skb, netdev, daddr, dev->netdev->dev_addr);
 
 	err = dev_hard_header(skb, netdev, ETH_P_IPV6, NULL, NULL, 0);
-	if (err < 0)
+	if (err < 0) {
+		if (lowpan_cb(skb)->peer) {
+			lowpan_peer_put(lowpan_cb(skb)->peer);
+			lowpan_cb(skb)->peer = NULL;
+		}
 		return err;
+	}
 
 	return status;
 }
@@ -486,6 +536,10 @@ static int send_mcast_pkt(struct sk_buff *skb, struct net_device *netdev)
 			int ret;
 
 			local_skb = skb_clone(skb, GFP_ATOMIC);
+			if (!local_skb) {
+				err = -ENOMEM;
+				continue;
+			}
 
 			BT_DBG("xmit %s to %pMR type %u IP %pI6c chan %p",
 			       netdev->name,
@@ -529,11 +583,17 @@ static netdev_tx_t bt_xmit(struct sk_buff *skb, struct net_device *netdev)
 	}
 
 	if (err) {
-		if (lowpan_cb(skb)->chan) {
+		struct lowpan_peer *peer = lowpan_cb(skb)->peer;
+
+		if (peer) {
+			struct l2cap_chan *chan = peer->chan;
+
 			BT_DBG("xmit %s to %pMR type %u IP %pI6c chan %p",
 			       netdev->name, &addr, addr_type,
-			       &lowpan_cb(skb)->addr, lowpan_cb(skb)->chan);
-			err = send_pkt(lowpan_cb(skb)->chan, skb, netdev);
+			       &lowpan_cb(skb)->addr, chan);
+			err = send_pkt(chan, skb, netdev);
+			lowpan_peer_put(peer);
+			lowpan_cb(skb)->peer = NULL;
 		} else {
 			err = -ENOENT;
 		}
@@ -649,6 +709,8 @@ static struct l2cap_chan *add_peer_chan(struct l2cap_chan *chan,
 	if (!peer)
 		return NULL;
 
+	INIT_WORK(&peer->destroy_work, lowpan_peer_destroy);
+	refcount_set(&peer->refcnt, 1);
 	peer->chan = chan;
 
 	baswap((void *)peer->lladdr, &chan->dst);
@@ -822,8 +884,6 @@ static void chan_close_cb(struct l2cap_chan *chan)
 			       last ? "last " : "1 ", peer);
 			BT_DBG("chan %p orig refcnt %u", chan,
 			       kref_read(&chan->kref));
-
-			l2cap_chan_put(chan);
 			break;
 		}
 	}
@@ -938,6 +998,7 @@ static int bt_6lowpan_connect(bdaddr_t *addr, u8 dst_type)
 static int bt_6lowpan_disconnect(struct l2cap_conn *conn, u8 dst_type)
 {
 	struct lowpan_peer *peer;
+	struct l2cap_chan *chan;
 
 	BT_DBG("conn %p dst type %u", conn, dst_type);
 
@@ -945,11 +1006,14 @@ static int bt_6lowpan_disconnect(struct l2cap_conn *conn, u8 dst_type)
 	if (!peer)
 		return -ENOENT;
 
-	BT_DBG("peer %p chan %p", peer, peer->chan);
+	chan = peer->chan;
+
+	BT_DBG("peer %p chan %p", peer, chan);
 
-	l2cap_chan_lock(peer->chan);
-	l2cap_chan_close(peer->chan, ENOENT);
-	l2cap_chan_unlock(peer->chan);
+	l2cap_chan_lock(chan);
+	l2cap_chan_close(chan, ENOENT);
+	l2cap_chan_unlock(chan);
+	lowpan_peer_put(peer);
 
 	return 0;
 }
@@ -1189,6 +1253,7 @@ static ssize_t lowpan_control_write(struct file *fp,
 			peer = lookup_peer(conn);
 			if (peer) {
 				BT_DBG("6LoWPAN connection already exists");
+				lowpan_peer_put(peer);
 				return -EALREADY;
 			}
 
@@ -1320,6 +1385,12 @@ static struct notifier_block bt_6lowpan_dev_notifier = {
 
 static int __init bt_6lowpan_init(void)
 {
+	int err;
+
+	lowpan_peer_wq = alloc_ordered_workqueue("bt_6lowpan_peer", 0);
+	if (!lowpan_peer_wq)
+		return -ENOMEM;
+
 	lowpan_enable_debugfs = debugfs_create_file_unsafe("6lowpan_enable",
 							   0644, bt_debugfs,
 							   NULL,
@@ -1328,7 +1399,14 @@ static int __init bt_6lowpan_init(void)
 						     bt_debugfs, NULL,
 						     &lowpan_control_fops);
 
-	return register_netdevice_notifier(&bt_6lowpan_dev_notifier);
+	err = register_netdevice_notifier(&bt_6lowpan_dev_notifier);
+	if (err) {
+		debugfs_remove(lowpan_enable_debugfs);
+		debugfs_remove(lowpan_control_debugfs);
+		destroy_workqueue(lowpan_peer_wq);
+	}
+
+	return err;
 }
 
 static void __exit bt_6lowpan_exit(void)
@@ -1346,6 +1424,8 @@ static void __exit bt_6lowpan_exit(void)
 	disconnect_devices();
 
 	unregister_netdevice_notifier(&bt_6lowpan_dev_notifier);
+	rcu_barrier();
+	destroy_workqueue(lowpan_peer_wq);
 }
 
 module_init(bt_6lowpan_init);
-- 
2.43.0


^ permalink raw reply related

* [PATCH v3] Bluetooth: 6lowpan: Fix peer and channel lifetime during teardown
From: Zhang Cen @ 2026-05-11 17:18 UTC (permalink / raw)
  To: Marcel Holtmann, Luiz Augusto von Dentz
  Cc: linux-bluetooth, linux-kernel, zerocling0077, 2045gemini,
	Zhang Cen
In-Reply-To: <20260510040424.2054614-1-rollkingzzc@gmail.com>

A 6LoWPAN peer keeps a protocol-owned L2CAP channel reference in
peer->chan. chan_close_cb() removes the peer from the RCU list when the
channel is closed, but the old code also dropped that channel reference
before the peer object stopped being observable by in-flight RCU readers.

The buggy scenario involves two paths, with each column showing the order
within that path:

  close path:                            lookup/transmit path:
  l2cap_conn_del() or another            setup_header(), send_mcast_pkt(),
  close path holds a temporary           bt_6lowpan_disconnect(), or
  channel reference                      chan_recv_cb() observes a peer

  l2cap_chan_del() drops the             the reader leaves the RCU read
  connection-owned channel reference     side critical section, or keeps
                                         using peer->chan inside it

  chan_close_cb() removes the peer       the reader reads channel fields,
  from the peer list                     locks the channel, or sends
                                         through peer->chan

  the old chan_close_cb() dropped
  the peer-owned channel reference

  the close caller releases its
  temporary channel reference

If the close path consumes the peer-owned channel reference before a
reader finishes with peer->chan, the reader can use a freed l2cap_chan.
The lookup helpers that returned raw peer pointers after rcu_read_unlock()
also let callers outlive the peer object's kfree_rcu() lifetime.

Give lowpan_peer its own refcount and make RCU lookups acquire a peer
reference before returning a peer to code that runs outside the read-side
critical section. Keep the peer-owned L2CAP channel reference attached to
the peer until the last peer reference is dropped.

The last peer put can happen from an RCU callback or from the network
transmit path, so do not drop the final L2CAP channel reference directly
from lowpan_peer_put(). Queue final peer destruction to a dedicated
workqueue and release the channel and module references there. Store the
peer, rather than a temporary channel reference, in the skb control block
for unicast transmit, and put the peer after the send path is done.
Multicast continues to use peer->chan only while it is inside the RCU
read-side critical section.

Wait for queued RCU callbacks and peer-destroy work during module exit so
asynchronous peer destruction cannot outlive the module text.

Signed-off-by: Zhang Cen <rollkingzzc@gmail.com>
---
 net/bluetooth/6lowpan.c | 128 ++++++++++++++++++++++++++++++++--------
 1 file changed, 104 insertions(+), 24 deletions(-)

diff --git a/net/bluetooth/6lowpan.c b/net/bluetooth/6lowpan.c
index 23a229ab6..7a1b0d7b0 100644
--- a/net/bluetooth/6lowpan.c
+++ b/net/bluetooth/6lowpan.c
@@ -9,6 +9,8 @@
 #include <linux/etherdevice.h>
 #include <linux/module.h>
 #include <linux/debugfs.h>
+#include <linux/refcount.h>
+#include <linux/workqueue.h>
 
 #include <net/ipv6.h>
 #include <net/ip6_route.h>
@@ -29,10 +31,12 @@ static struct dentry *lowpan_control_debugfs;
 
 #define IFACE_NAME_TEMPLATE "bt%d"
 
+struct lowpan_peer;
+
 struct skb_cb {
 	struct in6_addr addr;
 	struct in6_addr gw;
-	struct l2cap_chan *chan;
+	struct lowpan_peer *peer;
 };
 #define lowpan_cb(skb) ((struct skb_cb *)((skb)->cb))
 
@@ -52,6 +56,7 @@ static bool enable_6lowpan;
  */
 static struct l2cap_chan *listen_chan;
 static DEFINE_MUTEX(set_lock);
+static struct workqueue_struct *lowpan_peer_wq;
 
 enum {
 	LOWPAN_PEER_CLOSING,
@@ -61,6 +66,8 @@ enum {
 struct lowpan_peer {
 	struct list_head list;
 	struct rcu_head rcu;
+	struct work_struct destroy_work;
+	refcount_t refcnt;
 	struct l2cap_chan *chan;
 
 	/* peer addresses in various formats */
@@ -95,13 +102,39 @@ static inline void peer_add(struct lowpan_btle_dev *dev,
 	atomic_inc(&dev->peer_count);
 }
 
+static inline bool lowpan_peer_get_unless_zero(struct lowpan_peer *peer)
+{
+	return refcount_inc_not_zero(&peer->refcnt);
+}
+
+static void lowpan_peer_destroy(struct work_struct *work)
+{
+	struct lowpan_peer *peer = container_of(work, struct lowpan_peer,
+						destroy_work);
+
+	l2cap_chan_put(peer->chan);
+	kfree(peer);
+	module_put(THIS_MODULE);
+}
+
+static void lowpan_peer_put(struct lowpan_peer *peer)
+{
+	if (refcount_dec_and_test(&peer->refcnt))
+		queue_work(lowpan_peer_wq, &peer->destroy_work);
+}
+
+static void lowpan_peer_put_rcu(struct rcu_head *rcu)
+{
+	struct lowpan_peer *peer = container_of(rcu, struct lowpan_peer, rcu);
+
+	lowpan_peer_put(peer);
+}
+
 static inline bool peer_del(struct lowpan_btle_dev *dev,
 			    struct lowpan_peer *peer)
 {
 	list_del_rcu(&peer->list);
-	kfree_rcu(peer, rcu);
-
-	module_put(THIS_MODULE);
+	call_rcu(&peer->rcu, lowpan_peer_put_rcu);
 
 	if (atomic_dec_and_test(&dev->peer_count)) {
 		BT_DBG("last peer");
@@ -179,7 +212,8 @@ static inline struct lowpan_peer *peer_lookup_dst(struct lowpan_btle_dev *dev,
 		       &peer->chan->dst, peer->chan->dst_type,
 		       &peer->peer_addr);
 
-		if (!ipv6_addr_cmp(&peer->peer_addr, nexthop)) {
+		if (!ipv6_addr_cmp(&peer->peer_addr, nexthop) &&
+		    lowpan_peer_get_unless_zero(peer)) {
 			rcu_read_unlock();
 			return peer;
 		}
@@ -189,7 +223,8 @@ static inline struct lowpan_peer *peer_lookup_dst(struct lowpan_btle_dev *dev,
 	neigh = __ipv6_neigh_lookup(dev->netdev, nexthop);
 	if (neigh) {
 		list_for_each_entry_rcu(peer, &dev->peers, list) {
-			if (!memcmp(neigh->ha, peer->lladdr, ETH_ALEN)) {
+			if (!memcmp(neigh->ha, peer->lladdr, ETH_ALEN) &&
+			    lowpan_peer_get_unless_zero(peer)) {
 				neigh_release(neigh);
 				rcu_read_unlock();
 				return peer;
@@ -212,8 +247,9 @@ static struct lowpan_peer *lookup_peer(struct l2cap_conn *conn)
 
 	list_for_each_entry_rcu(entry, &bt_6lowpan_devices, list) {
 		peer = __peer_lookup_conn(entry, conn);
-		if (peer)
+		if (peer && lowpan_peer_get_unless_zero(peer))
 			break;
+		peer = NULL;
 	}
 
 	rcu_read_unlock();
@@ -361,8 +397,10 @@ static int chan_recv_cb(struct l2cap_chan *chan, struct sk_buff *skb)
 		return -ENOENT;
 
 	dev = lookup_dev(chan->conn);
-	if (!dev || !dev->netdev)
+	if (!dev || !dev->netdev) {
+		lowpan_peer_put(peer);
 		return -ENOENT;
+	}
 
 	err = recv_pkt(skb, dev->netdev, peer);
 	if (err) {
@@ -370,6 +408,8 @@ static int chan_recv_cb(struct l2cap_chan *chan, struct sk_buff *skb)
 		err = -EAGAIN;
 	}
 
+	lowpan_peer_put(peer);
+
 	return err;
 }
 
@@ -380,6 +420,8 @@ static int setup_header(struct sk_buff *skb, struct net_device *netdev,
 	struct ipv6hdr *hdr;
 	struct lowpan_btle_dev *dev;
 	struct lowpan_peer *peer;
+	struct l2cap_chan *chan;
+	u8 peer_lladdr[ETH_ALEN];
 	u8 *daddr;
 	int err, status = 0;
 
@@ -388,9 +430,9 @@ static int setup_header(struct sk_buff *skb, struct net_device *netdev,
 	dev = lowpan_btle_dev(netdev);
 
 	memcpy(&ipv6_daddr, &hdr->daddr, sizeof(ipv6_daddr));
+	lowpan_cb(skb)->peer = NULL;
 
 	if (ipv6_addr_is_multicast(&ipv6_daddr)) {
-		lowpan_cb(skb)->chan = NULL;
 		daddr = NULL;
 	} else {
 		BT_DBG("dest IP %pI6c", &ipv6_daddr);
@@ -406,10 +448,13 @@ static int setup_header(struct sk_buff *skb, struct net_device *netdev,
 			return -ENOENT;
 		}
 
-		daddr = peer->lladdr;
-		*peer_addr = peer->chan->dst;
-		*peer_addr_type = peer->chan->dst_type;
-		lowpan_cb(skb)->chan = peer->chan;
+		chan = peer->chan;
+		memcpy(peer_lladdr, peer->lladdr, ETH_ALEN);
+		*peer_addr = chan->dst;
+		*peer_addr_type = chan->dst_type;
+		lowpan_cb(skb)->peer = peer;
+
+		daddr = peer_lladdr;
 
 		status = 1;
 	}
@@ -417,8 +462,13 @@ static int setup_header(struct sk_buff *skb, struct net_device *netdev,
 	lowpan_header_compress(skb, netdev, daddr, dev->netdev->dev_addr);
 
 	err = dev_hard_header(skb, netdev, ETH_P_IPV6, NULL, NULL, 0);
-	if (err < 0)
+	if (err < 0) {
+		if (lowpan_cb(skb)->peer) {
+			lowpan_peer_put(lowpan_cb(skb)->peer);
+			lowpan_cb(skb)->peer = NULL;
+		}
 		return err;
+	}
 
 	return status;
 }
@@ -486,6 +536,10 @@ static int send_mcast_pkt(struct sk_buff *skb, struct net_device *netdev)
 			int ret;
 
 			local_skb = skb_clone(skb, GFP_ATOMIC);
+			if (!local_skb) {
+				err = -ENOMEM;
+				continue;
+			}
 
 			BT_DBG("xmit %s to %pMR type %u IP %pI6c chan %p",
 			       netdev->name,
@@ -529,11 +583,17 @@ static netdev_tx_t bt_xmit(struct sk_buff *skb, struct net_device *netdev)
 	}
 
 	if (err) {
-		if (lowpan_cb(skb)->chan) {
+		struct lowpan_peer *peer = lowpan_cb(skb)->peer;
+
+		if (peer) {
+			struct l2cap_chan *chan = peer->chan;
+
 			BT_DBG("xmit %s to %pMR type %u IP %pI6c chan %p",
 			       netdev->name, &addr, addr_type,
-			       &lowpan_cb(skb)->addr, lowpan_cb(skb)->chan);
-			err = send_pkt(lowpan_cb(skb)->chan, skb, netdev);
+			       &lowpan_cb(skb)->addr, chan);
+			err = send_pkt(chan, skb, netdev);
+			lowpan_peer_put(peer);
+			lowpan_cb(skb)->peer = NULL;
 		} else {
 			err = -ENOENT;
 		}
@@ -649,6 +709,8 @@ static struct l2cap_chan *add_peer_chan(struct l2cap_chan *chan,
 	if (!peer)
 		return NULL;
 
+	INIT_WORK(&peer->destroy_work, lowpan_peer_destroy);
+	refcount_set(&peer->refcnt, 1);
 	peer->chan = chan;
 
 	baswap((void *)peer->lladdr, &chan->dst);
@@ -822,8 +884,6 @@ static void chan_close_cb(struct l2cap_chan *chan)
 			       last ? "last " : "1 ", peer);
 			BT_DBG("chan %p orig refcnt %u", chan,
 			       kref_read(&chan->kref));
-
-			l2cap_chan_put(chan);
 			break;
 		}
 	}
@@ -938,6 +998,7 @@ static int bt_6lowpan_connect(bdaddr_t *addr, u8 dst_type)
 static int bt_6lowpan_disconnect(struct l2cap_conn *conn, u8 dst_type)
 {
 	struct lowpan_peer *peer;
+	struct l2cap_chan *chan;
 
 	BT_DBG("conn %p dst type %u", conn, dst_type);
 
@@ -945,11 +1006,14 @@ static int bt_6lowpan_disconnect(struct l2cap_conn *conn, u8 dst_type)
 	if (!peer)
 		return -ENOENT;
 
-	BT_DBG("peer %p chan %p", peer, peer->chan);
+	chan = peer->chan;
+
+	BT_DBG("peer %p chan %p", peer, chan);
 
-	l2cap_chan_lock(peer->chan);
-	l2cap_chan_close(peer->chan, ENOENT);
-	l2cap_chan_unlock(peer->chan);
+	l2cap_chan_lock(chan);
+	l2cap_chan_close(chan, ENOENT);
+	l2cap_chan_unlock(chan);
+	lowpan_peer_put(peer);
 
 	return 0;
 }
@@ -1189,6 +1253,7 @@ static ssize_t lowpan_control_write(struct file *fp,
 			peer = lookup_peer(conn);
 			if (peer) {
 				BT_DBG("6LoWPAN connection already exists");
+				lowpan_peer_put(peer);
 				return -EALREADY;
 			}
 
@@ -1320,6 +1385,12 @@ static struct notifier_block bt_6lowpan_dev_notifier = {
 
 static int __init bt_6lowpan_init(void)
 {
+	int err;
+
+	lowpan_peer_wq = alloc_ordered_workqueue("bt_6lowpan_peer", 0);
+	if (!lowpan_peer_wq)
+		return -ENOMEM;
+
 	lowpan_enable_debugfs = debugfs_create_file_unsafe("6lowpan_enable",
 							   0644, bt_debugfs,
 							   NULL,
@@ -1328,7 +1399,14 @@ static int __init bt_6lowpan_init(void)
 						     bt_debugfs, NULL,
 						     &lowpan_control_fops);
 
-	return register_netdevice_notifier(&bt_6lowpan_dev_notifier);
+	err = register_netdevice_notifier(&bt_6lowpan_dev_notifier);
+	if (err) {
+		debugfs_remove(lowpan_enable_debugfs);
+		debugfs_remove(lowpan_control_debugfs);
+		destroy_workqueue(lowpan_peer_wq);
+	}
+
+	return err;
 }
 
 static void __exit bt_6lowpan_exit(void)
@@ -1346,6 +1424,8 @@ static void __exit bt_6lowpan_exit(void)
 	disconnect_devices();
 
 	unregister_netdevice_notifier(&bt_6lowpan_dev_notifier);
+	rcu_barrier();
+	destroy_workqueue(lowpan_peer_wq);
 }
 
 module_init(bt_6lowpan_init);
-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH v2] Bluetooth: 6lowpan: Fix peer and channel lifetime during teardown
From: Cen Zhang @ 2026-05-11 17:11 UTC (permalink / raw)
  To: Luiz Augusto von Dentz
  Cc: Marcel Holtmann, linux-bluetooth, linux-kernel, Hillf Danton,
	zerocling0077, hanguidong02
In-Reply-To: <CABBYNZLWzkDcZW+32-a1AdrvVzEfqs0BqGoH_jH9NBForCSHWA@mail.gmail.com>

Hi Luiz,

Thanks for pointing this out.

I'll take another look at the lifetime and context issues raised there
and rework the patch accordingly. I'll send a v3 after addressing them.

Thanks,
Zhang Cen

Luiz Augusto von Dentz <luiz.dentz@gmail.com> 于2026年5月12日周二 00:20写道:
>
> Hi,
>
> On Sun, May 10, 2026 at 12:04 AM Zhang Cen <rollkingzzc@gmail.com> wrote:
> >
> > 6LoWPAN peers keep a protocol-owned L2CAP channel reference in
> > peer->chan. chan_close_cb() removes the peer from the RCU list when the
> > channel is closed, but the old code also dropped that channel reference
> > before the peer object stopped being observable by in-flight RCU readers.
> >
> > That can leave peer readers with a live lowpan_peer that still points at
> > an l2cap_chan whose last reference has already been released. Some
> > lookup helpers also returned raw peer pointers after dropping RCU
> > protection, so keeping only the channel alive until the RCU callback
> > still leaves the peer lifetime rules implicit.
> >
> > Make the peer lifetime explicit. Give each lowpan_peer a refcount, have
> > RCU lookups take a peer reference before returning a peer outside the
> > read-side critical section, and make peer_del() only unlink the peer and
> > schedule an RCU callback to drop the list owner reference after the grace
> > period. Release the peer-owned L2CAP channel reference and module
> > reference from the final peer put.
> >
> > Keep temporary channel references in transmit and disconnect paths that
> > cache or send through peer->chan after lookup.
> >
> > The buggy scenario involves two paths, with each column showing the order within that path:
> >
> > L2CAP peer teardown:                   Concurrent peer reader:
> >   1. A close path holds a                1. A reader such as
> >      temporary reference to the             lookup_peer(),
> >      channel                                peer_lookup_dst(),
> >                                             send_mcast_pkt(), or
> >                                             bt_6lowpan_disconnect()
> >                                             observes the peer
> >   2. l2cap_chan_del() drops the          2. The reader keeps using the
> >      connection-owned channel               raw peer or peer->chan after
> >      reference                              RCU lookup protection ends
> >   3. chan_close_cb() removes the         3. The reader reads channel
> >      lowpan_peer from the peer              fields, locks the channel, or
> >      list                                   sends through the channel
> >                                             pointer
> >   4. The old chan_close_cb()
> >      dropped the peer-owned
> >      channel reference before the
> >      peer RCU grace period ended
> >   5. The close caller releases its
> >      temporary channel reference
> >
> > If the reader reaches its channel use after teardown releases the peer-
> > owned channel reference and the close caller releases its temporary
> > reference, it can use a freed channel; raw peer returns can also outlive
> > the peer RCU callback.
> >
> > lowpan_peer objects remain RCU-observable after peer_del() unlinks them.
> >
> > Signed-off-by: Zhang Cen <rollkingzzc@gmail.com>
> >
> > ---
> > diff --git a/net/bluetooth/6lowpan.c b/net/bluetooth/6lowpan.c
> > index 2f03b780b40d..7334a78f5bfb 100644
> > --- a/net/bluetooth/6lowpan.c
> > +++ b/net/bluetooth/6lowpan.c
> > @@ -9,6 +9,7 @@
> >  #include <linux/etherdevice.h>
> >  #include <linux/module.h>
> >  #include <linux/debugfs.h>
> > +#include <linux/refcount.h>
> >
> >  #include <net/ipv6.h>
> >  #include <net/ip6_route.h>
> > @@ -61,6 +62,7 @@ enum {
> >  struct lowpan_peer {
> >         struct list_head list;
> >         struct rcu_head rcu;
> > +       refcount_t refcnt;
> >         struct l2cap_chan *chan;
> >
> >         /* peer addresses in various formats */
> > @@ -95,13 +97,33 @@ static inline void peer_add(struct lowpan_btle_dev *dev,
> >         atomic_inc(&dev->peer_count);
> >  }
> >
> > +static inline bool lowpan_peer_get_unless_zero(struct lowpan_peer *peer)
> > +{
> > +       return refcount_inc_not_zero(&peer->refcnt);
> > +}
> > +
> > +static void lowpan_peer_put(struct lowpan_peer *peer)
> > +{
> > +       if (!refcount_dec_and_test(&peer->refcnt))
> > +               return;
> > +
> > +       l2cap_chan_put(peer->chan);
> > +       kfree(peer);
> > +       module_put(THIS_MODULE);
> > +}
> > +
> > +static void lowpan_peer_put_rcu(struct rcu_head *rcu)
> > +{
> > +       struct lowpan_peer *peer = container_of(rcu, struct lowpan_peer, rcu);
> > +
> > +       lowpan_peer_put(peer);
> > +}
> > +
> >  static inline bool peer_del(struct lowpan_btle_dev *dev,
> >                             struct lowpan_peer *peer)
> >  {
> >         list_del_rcu(&peer->list);
> > -       kfree_rcu(peer, rcu);
> > -
> > -       module_put(THIS_MODULE);
> > +       call_rcu(&peer->rcu, lowpan_peer_put_rcu);
> >
> >         if (atomic_dec_and_test(&dev->peer_count)) {
> >                 BT_DBG("last peer");
> > @@ -179,7 +201,8 @@ static inline struct lowpan_peer *peer_lookup_dst(struct lowpan_btle_dev *dev,
> >                        &peer->chan->dst, peer->chan->dst_type,
> >                        &peer->peer_addr);
> >
> > -               if (!ipv6_addr_cmp(&peer->peer_addr, nexthop)) {
> > +               if (!ipv6_addr_cmp(&peer->peer_addr, nexthop) &&
> > +                   lowpan_peer_get_unless_zero(peer)) {
> >                         rcu_read_unlock();
> >                         return peer;
> >                 }
> > @@ -189,7 +212,8 @@ static inline struct lowpan_peer *peer_lookup_dst(struct lowpan_btle_dev *dev,
> >         neigh = __ipv6_neigh_lookup(dev->netdev, nexthop);
> >         if (neigh) {
> >                 list_for_each_entry_rcu(peer, &dev->peers, list) {
> > -                       if (!memcmp(neigh->ha, peer->lladdr, ETH_ALEN)) {
> > +                       if (!memcmp(neigh->ha, peer->lladdr, ETH_ALEN) &&
> > +                           lowpan_peer_get_unless_zero(peer)) {
> >                                 neigh_release(neigh);
> >                                 rcu_read_unlock();
> >                                 return peer;
> > @@ -212,8 +236,9 @@ static struct lowpan_peer *lookup_peer(struct l2cap_conn *conn)
> >
> >         list_for_each_entry_rcu(entry, &bt_6lowpan_devices, list) {
> >                 peer = __peer_lookup_conn(entry, conn);
> > -               if (peer)
> > +               if (peer && lowpan_peer_get_unless_zero(peer))
> >                         break;
> > +               peer = NULL;
> >         }
> >
> >         rcu_read_unlock();
> > @@ -361,8 +386,10 @@ static int chan_recv_cb(struct l2cap_chan *chan, struct sk_buff *skb)
> >                 return -ENOENT;
> >
> >         dev = lookup_dev(chan->conn);
> > -       if (!dev || !dev->netdev)
> > +       if (!dev || !dev->netdev) {
> > +               lowpan_peer_put(peer);
> >                 return -ENOENT;
> > +       }
> >
> >         err = recv_pkt(skb, dev->netdev, peer);
> >         if (err) {
> > @@ -370,6 +397,8 @@ static int chan_recv_cb(struct l2cap_chan *chan, struct sk_buff *skb)
> >                 err = -EAGAIN;
> >         }
> >
> > +       lowpan_peer_put(peer);
> > +
> >         return err;
> >  }
> >
> > @@ -380,6 +409,8 @@ static int setup_header(struct sk_buff *skb, struct net_device *netdev,
> >         struct ipv6hdr *hdr;
> >         struct lowpan_btle_dev *dev;
> >         struct lowpan_peer *peer;
> > +       struct l2cap_chan *chan;
> > +       u8 peer_lladdr[ETH_ALEN];
> >         u8 *daddr;
> >         int err, status = 0;
> >
> > @@ -388,9 +419,9 @@ static int setup_header(struct sk_buff *skb, struct net_device *netdev,
> >         dev = lowpan_btle_dev(netdev);
> >
> >         memcpy(&ipv6_daddr, &hdr->daddr, sizeof(ipv6_daddr));
> > +       lowpan_cb(skb)->chan = NULL;
> >
> >         if (ipv6_addr_is_multicast(&ipv6_daddr)) {
> > -               lowpan_cb(skb)->chan = NULL;
> >                 daddr = NULL;
> >         } else {
> >                 BT_DBG("dest IP %pI6c", &ipv6_daddr);
> > @@ -406,10 +437,15 @@ static int setup_header(struct sk_buff *skb, struct net_device *netdev,
> >                         return -ENOENT;
> >                 }
> >
> > -               daddr = peer->lladdr;
> > -               *peer_addr = peer->chan->dst;
> > -               *peer_addr_type = peer->chan->dst_type;
> > -               lowpan_cb(skb)->chan = peer->chan;
> > +               chan = peer->chan;
> > +               l2cap_chan_hold(chan);
> > +               memcpy(peer_lladdr, peer->lladdr, ETH_ALEN);
> > +               *peer_addr = chan->dst;
> > +               *peer_addr_type = chan->dst_type;
> > +               lowpan_cb(skb)->chan = chan;
> > +               lowpan_peer_put(peer);
> > +
> > +               daddr = peer_lladdr;
> >
> >                 status = 1;
> >         }
> > @@ -417,8 +453,13 @@ static int setup_header(struct sk_buff *skb, struct net_device *netdev,
> >         lowpan_header_compress(skb, netdev, daddr, dev->netdev->dev_addr);
> >
> >         err = dev_hard_header(skb, netdev, ETH_P_IPV6, NULL, NULL, 0);
> > -       if (err < 0)
> > +       if (err < 0) {
> > +               if (lowpan_cb(skb)->chan) {
> > +                       l2cap_chan_put(lowpan_cb(skb)->chan);
> > +                       lowpan_cb(skb)->chan = NULL;
> > +               }
> >                 return err;
> > +       }
> >
> >         return status;
> >  }
> > @@ -483,15 +524,23 @@ static int send_mcast_pkt(struct sk_buff *skb, struct net_device *netdev)
> >                 dev = lowpan_btle_dev(entry->netdev);
> >
> >                 list_for_each_entry_rcu(pentry, &dev->peers, list) {
> > +                       struct l2cap_chan *chan = pentry->chan;
> >                         int ret;
> >
> >                         local_skb = skb_clone(skb, GFP_ATOMIC);
> > +                       if (!local_skb) {
> > +                               err = -ENOMEM;
> > +                               continue;
> > +                       }
> > +
> > +                       l2cap_chan_hold(chan);
> >
> >                         BT_DBG("xmit %s to %pMR type %u IP %pI6c chan %p",
> >                                netdev->name,
> > -                              &pentry->chan->dst, pentry->chan->dst_type,
> > -                              &pentry->peer_addr, pentry->chan);
> > -                       ret = send_pkt(pentry->chan, local_skb, netdev);
> > +                              &chan->dst, chan->dst_type,
> > +                              &pentry->peer_addr, chan);
> > +                       ret = send_pkt(chan, local_skb, netdev);
> > +                       l2cap_chan_put(chan);
> >                         if (ret < 0)
> >                                 err = ret;
> >
> > @@ -530,10 +579,14 @@ static netdev_tx_t bt_xmit(struct sk_buff *skb, struct net_device *netdev)
> >
> >         if (err) {
> >                 if (lowpan_cb(skb)->chan) {
> > +                       struct l2cap_chan *chan = lowpan_cb(skb)->chan;
> > +
> >                         BT_DBG("xmit %s to %pMR type %u IP %pI6c chan %p",
> >                                netdev->name, &addr, addr_type,
> > -                              &lowpan_cb(skb)->addr, lowpan_cb(skb)->chan);
> > -                       err = send_pkt(lowpan_cb(skb)->chan, skb, netdev);
> > +                              &lowpan_cb(skb)->addr, chan);
> > +                       err = send_pkt(chan, skb, netdev);
> > +                       l2cap_chan_put(chan);
> > +                       lowpan_cb(skb)->chan = NULL;
> >                 } else {
> >                         err = -ENOENT;
> >                 }
> > @@ -649,6 +702,7 @@ static struct l2cap_chan *add_peer_chan(struct l2cap_chan *chan,
> >         if (!peer)
> >                 return NULL;
> >
> > +       refcount_set(&peer->refcnt, 1);
> >         peer->chan = chan;
> >
> >         baswap((void *)peer->lladdr, &chan->dst);
> > @@ -802,8 +856,6 @@ static void chan_close_cb(struct l2cap_chan *chan)
> >                                last ? "last " : "1 ", peer);
> >                         BT_DBG("chan %p orig refcnt %u", chan,
> >                                kref_read(&chan->kref));
> > -
> > -                       l2cap_chan_put(chan);
> >                         break;
> >                 }
> >         }
> > @@ -918,6 +970,7 @@ static int bt_6lowpan_connect(bdaddr_t *addr, u8 dst_type)
> >  static int bt_6lowpan_disconnect(struct l2cap_conn *conn, u8 dst_type)
> >  {
> >         struct lowpan_peer *peer;
> > +       struct l2cap_chan *chan;
> >
> >         BT_DBG("conn %p dst type %u", conn, dst_type);
> >
> > @@ -925,11 +978,16 @@ static int bt_6lowpan_disconnect(struct l2cap_conn *conn, u8 dst_type)
> >         if (!peer)
> >                 return -ENOENT;
> >
> > -       BT_DBG("peer %p chan %p", peer, peer->chan);
> > +       chan = peer->chan;
> > +       l2cap_chan_hold(chan);
> > +
> > +       BT_DBG("peer %p chan %p", peer, chan);
> > +       lowpan_peer_put(peer);
> >
> > -       l2cap_chan_lock(peer->chan);
> > -       l2cap_chan_close(peer->chan, ENOENT);
> > -       l2cap_chan_unlock(peer->chan);
> > +       l2cap_chan_lock(chan);
> > +       l2cap_chan_close(chan, ENOENT);
> > +       l2cap_chan_unlock(chan);
> > +       l2cap_chan_put(chan);
> >
> >         return 0;
> >  }
> > @@ -1169,6 +1227,7 @@ static ssize_t lowpan_control_write(struct file *fp,
> >                         peer = lookup_peer(conn);
> >                         if (peer) {
> >                                 BT_DBG("6LoWPAN connection already exists");
> > +                               lowpan_peer_put(peer);
> >                                 return -EALREADY;
> >                         }
>
> AI got quite a few issues with this change:
>
> https://sashiko.dev/#/patchset/20260509173745.413473-1-rollkingzzc%40gmail.com
>
> --
> Luiz Augusto von Dentz

^ permalink raw reply

* RE: [v1] Bluetooth: L2CAP: Fix possible crash on l2cap_ecred_conn_rsp
From: bluez.test.bot @ 2026-05-11 17:10 UTC (permalink / raw)
  To: linux-bluetooth, luiz.dentz
In-Reply-To: <20260511162456.657014-1-luiz.dentz@gmail.com>

[-- Attachment #1: Type: text/plain, Size: 937 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/project/bluetooth/list/?series=1092945

---Test result---

Test Summary:
CheckPatch                    PASS      0.75 seconds
GitLint                       PASS      0.35 seconds
SubjectPrefix                 PASS      0.13 seconds
BuildKernel                   PASS      27.00 seconds
CheckAllWarning               PASS      29.77 seconds
CheckSparse                   PASS      28.32 seconds
BuildKernel32                 PASS      26.13 seconds
TestRunnerSetup               PASS      579.48 seconds
TestRunner_l2cap-tester       PASS      372.74 seconds
IncrementalBuild              PASS      26.99 seconds



https://github.com/bluez/bluetooth-next/pull/171

---
Regards,
Linux Bluetooth


^ permalink raw reply

* RE: [v1] Bluetooth: L2CAP: Fix possible crash on l2cap_ecred_conn_rsp
From: bluez.test.bot @ 2026-05-11 17:10 UTC (permalink / raw)
  To: linux-bluetooth, luiz.dentz
In-Reply-To: <20260511162306.654747-1-luiz.dentz@gmail.com>

[-- Attachment #1: Type: text/plain, Size: 937 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/project/bluetooth/list/?series=1092944

---Test result---

Test Summary:
CheckPatch                    PASS      0.53 seconds
GitLint                       PASS      0.21 seconds
SubjectPrefix                 PASS      0.06 seconds
BuildKernel                   PASS      26.94 seconds
CheckAllWarning               PASS      29.81 seconds
CheckSparse                   PASS      28.86 seconds
BuildKernel32                 PASS      26.45 seconds
TestRunnerSetup               PASS      581.85 seconds
TestRunner_l2cap-tester       PASS      378.38 seconds
IncrementalBuild              PASS      25.40 seconds



https://github.com/bluez/bluetooth-next/pull/170

---
Regards,
Linux Bluetooth


^ permalink raw reply

* [PATCH RESEND v4 1/1] Bluetooth: L2CAP: Fix use-after-free in l2cap_sock_new_connection_cb()
From: Siwei Zhang @ 2026-05-11 17:09 UTC (permalink / raw)
  To: Marcel Holtmann, Luiz Augusto von Dentz; +Cc: linux-bluetooth, Siwei Zhang
In-Reply-To: <20260511170929.709823-1-oss@fourdim.xyz>

l2cap_sock_new_connection_cb() accesses l2cap_pi(sk)->chan after
release_sock(parent). Once the parent lock is released, the child
socket sk can be freed by another task.

Save the channel pointer into a local variable while the parent lock
is still held to prevent this.

Fixes: 8ffb929098a5 ("Bluetooth: Remove parent socket usage from l2cap_core.c")
Cc: stable@kernel.org
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Siwei Zhang <oss@fourdim.xyz>
---
 net/bluetooth/6lowpan.c    |  5 +++++
 net/bluetooth/l2cap_core.c | 12 ++++++++++++
 net/bluetooth/l2cap_sock.c | 13 ++++++++++++-
 net/bluetooth/smp.c        |  5 +++++
 4 files changed, 34 insertions(+), 1 deletion(-)

diff --git a/net/bluetooth/6lowpan.c b/net/bluetooth/6lowpan.c
index 23a229ab6a33..71c1c04b61e5 100644
--- a/net/bluetooth/6lowpan.c
+++ b/net/bluetooth/6lowpan.c
@@ -755,6 +755,11 @@ static inline struct l2cap_chan *chan_new_conn_cb(struct l2cap_chan *pchan)
 
 	BT_DBG("chan %p pchan %p", chan, pchan);
 
+	/* Match the put that the caller of ops->new_connection() performs
+	 * once it is done with the returned channel pointer.
+	 */
+	l2cap_chan_hold(chan);
+
 	return chan;
 }
 
diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index 7701528f1167..0f6c3c651207 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -4071,6 +4071,9 @@ static void l2cap_connect(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd,
 
 	__l2cap_chan_add(conn, chan);
 
+	/* Drop the ops->new_connection() ref; conn list now pins chan. */
+	l2cap_chan_put(chan);
+
 	dcid = chan->scid;
 
 	__set_chan_timer(chan, chan->ops->get_sndtimeo(chan));
@@ -4970,6 +4973,9 @@ static int l2cap_le_connect_req(struct l2cap_conn *conn,
 
 	__l2cap_chan_add(conn, chan);
 
+	/* Drop the ops->new_connection() ref; conn list now pins chan. */
+	l2cap_chan_put(chan);
+
 	l2cap_le_flowctl_init(chan, __le16_to_cpu(req->credits));
 
 	dcid = chan->scid;
@@ -5194,6 +5200,9 @@ static inline int l2cap_ecred_conn_req(struct l2cap_conn *conn,
 
 		__l2cap_chan_add(conn, chan);
 
+		/* Drop the ops->new_connection() ref; conn list now pins chan. */
+		l2cap_chan_put(chan);
+
 		l2cap_ecred_init(chan, __le16_to_cpu(req->credits));
 
 		/* Init response */
@@ -7407,6 +7416,9 @@ static void l2cap_connect_cfm(struct hci_conn *hcon, u8 status)
 			chan->dst_type = dst_type;
 
 			__l2cap_chan_add(conn, chan);
+
+			/* Drop the ops->new_connection() ref; conn list now pins chan. */
+			l2cap_chan_put(chan);
 		}
 
 		l2cap_chan_unlock(pchan);
diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
index cf590a67d364..295c79cf5cf3 100644
--- a/net/bluetooth/l2cap_sock.c
+++ b/net/bluetooth/l2cap_sock.c
@@ -1497,6 +1497,7 @@ static void l2cap_sock_cleanup_listen(struct sock *parent)
 static struct l2cap_chan *l2cap_sock_new_connection_cb(struct l2cap_chan *chan)
 {
 	struct sock *sk, *parent = chan->data;
+	struct l2cap_chan *child_chan;
 
 	if (!parent)
 		return NULL;
@@ -1523,9 +1524,19 @@ static struct l2cap_chan *l2cap_sock_new_connection_cb(struct l2cap_chan *chan)
 
 	bt_accept_enqueue(parent, sk, false);
 
+	child_chan = l2cap_pi(sk)->chan;
+
+	/* Pin the channel for the caller. Once release_sock(parent) returns,
+	 * userspace can accept(2) and immediately close(2) the child socket,
+	 * which would drop the socket's references on the channel and free
+	 * it before the caller (e.g. l2cap_connect_req()) is done using the
+	 * returned pointer. The matching put is the caller's responsibility.
+	 */
+	l2cap_chan_hold(child_chan);
+
 	release_sock(parent);
 
-	return l2cap_pi(sk)->chan;
+	return child_chan;
 }
 
 static int l2cap_sock_recv_cb(struct l2cap_chan *chan, struct sk_buff *skb)
diff --git a/net/bluetooth/smp.c b/net/bluetooth/smp.c
index 1739c1989dbd..9796c3030434 100644
--- a/net/bluetooth/smp.c
+++ b/net/bluetooth/smp.c
@@ -3231,6 +3231,11 @@ static inline struct l2cap_chan *smp_new_conn_cb(struct l2cap_chan *pchan)
 
 	BT_DBG("created chan %p", chan);
 
+	/* Match the put that the caller of ops->new_connection() performs
+	 * once it is done with the returned channel pointer.
+	 */
+	l2cap_chan_hold(chan);
+
 	return chan;
 }
 
-- 
2.54.0


^ permalink raw reply related

* [PATCH RESEND v4 0/1] Bluetooth: L2CAP: Fix use-after-free in l2cap_sock_new_connection_cb()
From: Siwei Zhang @ 2026-05-11 17:09 UTC (permalink / raw)
  To: Marcel Holtmann, Luiz Augusto von Dentz; +Cc: linux-bluetooth, Siwei Zhang

This addresses v2 comments on https://sashiko.dev/#/patchset/20260415204842.2363950-1-oss%40fourdim.xyz .

Compared to v3, rebase against bluetooth-next.

Resend due to the missing version number.

Siwei Zhang (1):
  Bluetooth: L2CAP: Fix use-after-free in l2cap_sock_new_connection_cb()

 net/bluetooth/6lowpan.c    |  5 +++++
 net/bluetooth/l2cap_core.c | 12 ++++++++++++
 net/bluetooth/l2cap_sock.c | 13 ++++++++++++-
 net/bluetooth/smp.c        |  5 +++++
 4 files changed, 34 insertions(+), 1 deletion(-)

-- 
2.54.0


^ permalink raw reply

* RE: [v6] Bluetooth: hci_qca: Convert timeout from jiffies to ms
From: bluez.test.bot @ 2026-05-11 17:03 UTC (permalink / raw)
  To: linux-bluetooth, shuai.zhang
In-Reply-To: <20260511135837.3967550-1-shuai.zhang@oss.qualcomm.com>

[-- Attachment #1: Type: text/plain, Size: 2616 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/project/bluetooth/list/?series=1092831

---Test result---

Test Summary:
CheckPatch                    FAIL      0.78 seconds
GitLint                       FAIL      0.34 seconds
SubjectPrefix                 PASS      0.13 seconds
BuildKernel                   PASS      26.02 seconds
CheckAllWarning               PASS      28.87 seconds
CheckSparse                   PASS      27.80 seconds
BuildKernel32                 PASS      25.35 seconds
TestRunnerSetup               PASS      563.31 seconds
IncrementalBuild              PASS      25.36 seconds

Details
##############################
Test: CheckPatch - FAIL
Desc: Run checkpatch.pl script
Output:
[v6] Bluetooth: hci_qca: Convert timeout from jiffies to ms
ERROR: Please use git commit description style 'commit <12+ chars of sha1> ("<title line>")' - ie: 'commit c347ca17d62a ("Bluetooth: hci_qca: Fix missing wakeup during SSR memdump handling")'
#135: 
Wake timer depends on commit c347ca17d62a

total: 1 errors, 0 warnings, 101 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.

/github/workspace/src/patch/14565792.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: GitLint - FAIL
Desc: Run gitlint
Output:
[v6] Bluetooth: hci_qca: Convert timeout from jiffies to ms

WARNING: I3 - ignore-body-lines: gitlint will be switching from using Python regex 'match' (match beginning) to 'search' (match anywhere) semantics. Please review your ignore-body-lines.regex option accordingly. To remove this warning, set general.regex-style-search=True. More details: https://jorisroovers.github.io/gitlint/configuration/#regex-style-search
28: B1 Line exceeds max length (89>80): "- Rename macros by dropping the _MS suffix to reflect that they now yield jiffies values."
31: B1 Line exceeds max length (84>80): "  https://lore.kernel.org/all/20260429123802.1310681-1-shuai.zhang@oss.qualcomm.com/"
36: B1 Line exceeds max length (84>80): "  https://lore.kernel.org/all/20260327082941.1396521-1-shuai.zhang@oss.qualcomm.com/"


https://github.com/bluez/bluetooth-next/pull/168

---
Regards,
Linux Bluetooth


^ permalink raw reply

* Re: [PATCH 1/4] Bluetooth: hci_sync: pin conn across hci_le_create_conn_sync
From: Michael Bommarito @ 2026-05-11 17:01 UTC (permalink / raw)
  To: Luiz Augusto von Dentz
  Cc: Marcel Holtmann, linux-bluetooth, linux-kernel, Mat Martineau,
	netdev, stable, Pauli Virtanen, Aaron Esau
In-Reply-To: <CABBYNZL-f+AzFWdhvLcxdf0oCXbgr3AXqM1W2npOPZEv0gRA6w@mail.gmail.com>

On Mon, May 11, 2026 at 10:53 AM Luiz Augusto von Dentz
<luiz.dentz@gmail.com> wrote:
> Id suggest we dropped the once at the end so just hci_cmd_sync_queue_conn.
>
> > +                                       hci_cmd_sync_work_func_t func,
> > +                                       struct hci_conn *conn,
> > +                                       hci_cmd_sync_work_destroy_t destroy)
> > +{
> > +       int err;
> > +
> > +       err = hci_cmd_sync_queue_once(hdev, func, hci_conn_get(conn), destroy);
> > +       if (err)
> > +               hci_conn_put(conn);
> > +
> > +       return err;
>
> Then we incorporate return (err == -EEXIST) ? 0 : err; logic above, so
> I don't think any caller should require queuing multiple procedures
> for the same conn.

OK, good call.  I checked the other 10 callers for
hci_cmd_sync_queue_once and there isn't any variation today, so that
seems safe.

Do you want me to wait another 24-48 for others to weigh in or ship v2 now?

^ permalink raw reply

* Re: [PATCH] Bluetooth: L2CAP: ecred_reconfigure: send packed pdu, not stack pointer
From: patchwork-bot+bluetooth @ 2026-05-11 16:30 UTC (permalink / raw)
  To: Michael Bommarito
  Cc: marcel, luiz.dentz, johan.hedberg, linux-bluetooth, gustavoars,
	stable, linux-kernel
In-Reply-To: <20260511122641.437434-1-michael.bommarito@gmail.com>

Hello:

This patch was applied to bluetooth/bluetooth-next.git (master)
by Luiz Augusto von Dentz <luiz.von.dentz@intel.com>:

On Mon, 11 May 2026 08:26:41 -0400 you wrote:
> Commit 1c08108f3014 ("Bluetooth: L2CAP: Avoid -Wflex-array-member-not-at-end
> warnings") converted the on-stack request PDU in l2cap_ecred_reconfigure()
> from an explicit packed struct to DEFINE_RAW_FLEX(), but did not adjust the
> size and source-pointer arguments to l2cap_send_cmd():
> 
>   -    struct {
>   -            struct l2cap_ecred_reconf_req req;
>   -            __le16 scid;
>   -    } pdu;
>   +    DEFINE_RAW_FLEX(struct l2cap_ecred_reconf_req, pdu, scid, 1);
>        ...
>        l2cap_send_cmd(conn, chan->ident, L2CAP_ECRED_RECONF_REQ,
>                       sizeof(pdu), &pdu);
> 
> [...]

Here is the summary with links:
  - Bluetooth: L2CAP: ecred_reconfigure: send packed pdu, not stack pointer
    https://git.kernel.org/bluetooth/bluetooth-next/c/82b794a4b4df

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply

* [PATCH v1] Bluetooth: L2CAP: Fix possible crash on l2cap_ecred_conn_rsp
From: Luiz Augusto von Dentz @ 2026-05-11 16:24 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

If dcid is received for an already-assigned destination CID the spec
requires that both channels to be discarded, but calling l2cap_chan_del
may invalidate the tmp cursor created by list_for_each_entry_safe and in
fact it is the wrong procedure as the chan->dcid may be assigned
previously it really needs to be disconnected using
l2cap_send_disconn_req otherwise the remote peer would have no idea that
it shall consider to be disconnected.

Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
---
 net/bluetooth/l2cap_core.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index fdccd62ccca8..71846849e5b0 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -5293,7 +5293,11 @@ static inline int l2cap_ecred_conn_rsp(struct l2cap_conn *conn,
 			l2cap_chan_unlock(chan);
 			chan = __l2cap_get_chan_by_dcid(conn, dcid);
 			l2cap_chan_lock(chan);
-			l2cap_chan_del(chan, ECONNRESET);
+			/* Disconnect the original channel as it may be
+			 * considered connected since dcid has already been
+			 * assigned.
+			 */
+			l2cap_send_disconn_req(chan, ECONNRESET);
 			l2cap_chan_unlock(chan);
 			continue;
 		}
-- 
2.53.0


^ permalink raw reply related

* [PATCH v1] Bluetooth: L2CAP: Fix possible crash on l2cap_ecred_conn_rsp
From: Luiz Augusto von Dentz @ 2026-05-11 16:23 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

If dcid is received for an already-assigned destination CID the spec
requires that both channels to be discarded, but calling l2cap_chan_del
may invalidate the tmp cursor created by list_for_each_entry_safe and in
fact it is the wrong procedure as the chan->dcid may be assigned
previously it really needs to be disconnected using
l2cap_send_disconn_req otherwise the remote peer would have no idea that
it shall consider to be disconnected.

Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
---
 net/bluetooth/l2cap_core.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index fdccd62ccca8..71846849e5b0 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -5293,7 +5293,11 @@ static inline int l2cap_ecred_conn_rsp(struct l2cap_conn *conn,
 			l2cap_chan_unlock(chan);
 			chan = __l2cap_get_chan_by_dcid(conn, dcid);
 			l2cap_chan_lock(chan);
-			l2cap_chan_del(chan, ECONNRESET);
+			/* Disconnect the original channel as it may be
+			 * considered connected since dcid has already been
+			 * assigned.
+			 */
+			l2cap_send_disconn_req(chan, ECONNRESET);
 			l2cap_chan_unlock(chan);
 			continue;
 		}
-- 
2.53.0


^ permalink raw reply related

* Re: [PATCH v2] Bluetooth: 6lowpan: Fix peer and channel lifetime during teardown
From: Luiz Augusto von Dentz @ 2026-05-11 16:20 UTC (permalink / raw)
  To: Zhang Cen
  Cc: Marcel Holtmann, linux-bluetooth, linux-kernel, Hillf Danton,
	zerocling0077, hanguidong02
In-Reply-To: <20260510040424.2054614-1-rollkingzzc@gmail.com>

Hi,

On Sun, May 10, 2026 at 12:04 AM Zhang Cen <rollkingzzc@gmail.com> wrote:
>
> 6LoWPAN peers keep a protocol-owned L2CAP channel reference in
> peer->chan. chan_close_cb() removes the peer from the RCU list when the
> channel is closed, but the old code also dropped that channel reference
> before the peer object stopped being observable by in-flight RCU readers.
>
> That can leave peer readers with a live lowpan_peer that still points at
> an l2cap_chan whose last reference has already been released. Some
> lookup helpers also returned raw peer pointers after dropping RCU
> protection, so keeping only the channel alive until the RCU callback
> still leaves the peer lifetime rules implicit.
>
> Make the peer lifetime explicit. Give each lowpan_peer a refcount, have
> RCU lookups take a peer reference before returning a peer outside the
> read-side critical section, and make peer_del() only unlink the peer and
> schedule an RCU callback to drop the list owner reference after the grace
> period. Release the peer-owned L2CAP channel reference and module
> reference from the final peer put.
>
> Keep temporary channel references in transmit and disconnect paths that
> cache or send through peer->chan after lookup.
>
> The buggy scenario involves two paths, with each column showing the order within that path:
>
> L2CAP peer teardown:                   Concurrent peer reader:
>   1. A close path holds a                1. A reader such as
>      temporary reference to the             lookup_peer(),
>      channel                                peer_lookup_dst(),
>                                             send_mcast_pkt(), or
>                                             bt_6lowpan_disconnect()
>                                             observes the peer
>   2. l2cap_chan_del() drops the          2. The reader keeps using the
>      connection-owned channel               raw peer or peer->chan after
>      reference                              RCU lookup protection ends
>   3. chan_close_cb() removes the         3. The reader reads channel
>      lowpan_peer from the peer              fields, locks the channel, or
>      list                                   sends through the channel
>                                             pointer
>   4. The old chan_close_cb()
>      dropped the peer-owned
>      channel reference before the
>      peer RCU grace period ended
>   5. The close caller releases its
>      temporary channel reference
>
> If the reader reaches its channel use after teardown releases the peer-
> owned channel reference and the close caller releases its temporary
> reference, it can use a freed channel; raw peer returns can also outlive
> the peer RCU callback.
>
> lowpan_peer objects remain RCU-observable after peer_del() unlinks them.
>
> Signed-off-by: Zhang Cen <rollkingzzc@gmail.com>
>
> ---
> diff --git a/net/bluetooth/6lowpan.c b/net/bluetooth/6lowpan.c
> index 2f03b780b40d..7334a78f5bfb 100644
> --- a/net/bluetooth/6lowpan.c
> +++ b/net/bluetooth/6lowpan.c
> @@ -9,6 +9,7 @@
>  #include <linux/etherdevice.h>
>  #include <linux/module.h>
>  #include <linux/debugfs.h>
> +#include <linux/refcount.h>
>
>  #include <net/ipv6.h>
>  #include <net/ip6_route.h>
> @@ -61,6 +62,7 @@ enum {
>  struct lowpan_peer {
>         struct list_head list;
>         struct rcu_head rcu;
> +       refcount_t refcnt;
>         struct l2cap_chan *chan;
>
>         /* peer addresses in various formats */
> @@ -95,13 +97,33 @@ static inline void peer_add(struct lowpan_btle_dev *dev,
>         atomic_inc(&dev->peer_count);
>  }
>
> +static inline bool lowpan_peer_get_unless_zero(struct lowpan_peer *peer)
> +{
> +       return refcount_inc_not_zero(&peer->refcnt);
> +}
> +
> +static void lowpan_peer_put(struct lowpan_peer *peer)
> +{
> +       if (!refcount_dec_and_test(&peer->refcnt))
> +               return;
> +
> +       l2cap_chan_put(peer->chan);
> +       kfree(peer);
> +       module_put(THIS_MODULE);
> +}
> +
> +static void lowpan_peer_put_rcu(struct rcu_head *rcu)
> +{
> +       struct lowpan_peer *peer = container_of(rcu, struct lowpan_peer, rcu);
> +
> +       lowpan_peer_put(peer);
> +}
> +
>  static inline bool peer_del(struct lowpan_btle_dev *dev,
>                             struct lowpan_peer *peer)
>  {
>         list_del_rcu(&peer->list);
> -       kfree_rcu(peer, rcu);
> -
> -       module_put(THIS_MODULE);
> +       call_rcu(&peer->rcu, lowpan_peer_put_rcu);
>
>         if (atomic_dec_and_test(&dev->peer_count)) {
>                 BT_DBG("last peer");
> @@ -179,7 +201,8 @@ static inline struct lowpan_peer *peer_lookup_dst(struct lowpan_btle_dev *dev,
>                        &peer->chan->dst, peer->chan->dst_type,
>                        &peer->peer_addr);
>
> -               if (!ipv6_addr_cmp(&peer->peer_addr, nexthop)) {
> +               if (!ipv6_addr_cmp(&peer->peer_addr, nexthop) &&
> +                   lowpan_peer_get_unless_zero(peer)) {
>                         rcu_read_unlock();
>                         return peer;
>                 }
> @@ -189,7 +212,8 @@ static inline struct lowpan_peer *peer_lookup_dst(struct lowpan_btle_dev *dev,
>         neigh = __ipv6_neigh_lookup(dev->netdev, nexthop);
>         if (neigh) {
>                 list_for_each_entry_rcu(peer, &dev->peers, list) {
> -                       if (!memcmp(neigh->ha, peer->lladdr, ETH_ALEN)) {
> +                       if (!memcmp(neigh->ha, peer->lladdr, ETH_ALEN) &&
> +                           lowpan_peer_get_unless_zero(peer)) {
>                                 neigh_release(neigh);
>                                 rcu_read_unlock();
>                                 return peer;
> @@ -212,8 +236,9 @@ static struct lowpan_peer *lookup_peer(struct l2cap_conn *conn)
>
>         list_for_each_entry_rcu(entry, &bt_6lowpan_devices, list) {
>                 peer = __peer_lookup_conn(entry, conn);
> -               if (peer)
> +               if (peer && lowpan_peer_get_unless_zero(peer))
>                         break;
> +               peer = NULL;
>         }
>
>         rcu_read_unlock();
> @@ -361,8 +386,10 @@ static int chan_recv_cb(struct l2cap_chan *chan, struct sk_buff *skb)
>                 return -ENOENT;
>
>         dev = lookup_dev(chan->conn);
> -       if (!dev || !dev->netdev)
> +       if (!dev || !dev->netdev) {
> +               lowpan_peer_put(peer);
>                 return -ENOENT;
> +       }
>
>         err = recv_pkt(skb, dev->netdev, peer);
>         if (err) {
> @@ -370,6 +397,8 @@ static int chan_recv_cb(struct l2cap_chan *chan, struct sk_buff *skb)
>                 err = -EAGAIN;
>         }
>
> +       lowpan_peer_put(peer);
> +
>         return err;
>  }
>
> @@ -380,6 +409,8 @@ static int setup_header(struct sk_buff *skb, struct net_device *netdev,
>         struct ipv6hdr *hdr;
>         struct lowpan_btle_dev *dev;
>         struct lowpan_peer *peer;
> +       struct l2cap_chan *chan;
> +       u8 peer_lladdr[ETH_ALEN];
>         u8 *daddr;
>         int err, status = 0;
>
> @@ -388,9 +419,9 @@ static int setup_header(struct sk_buff *skb, struct net_device *netdev,
>         dev = lowpan_btle_dev(netdev);
>
>         memcpy(&ipv6_daddr, &hdr->daddr, sizeof(ipv6_daddr));
> +       lowpan_cb(skb)->chan = NULL;
>
>         if (ipv6_addr_is_multicast(&ipv6_daddr)) {
> -               lowpan_cb(skb)->chan = NULL;
>                 daddr = NULL;
>         } else {
>                 BT_DBG("dest IP %pI6c", &ipv6_daddr);
> @@ -406,10 +437,15 @@ static int setup_header(struct sk_buff *skb, struct net_device *netdev,
>                         return -ENOENT;
>                 }
>
> -               daddr = peer->lladdr;
> -               *peer_addr = peer->chan->dst;
> -               *peer_addr_type = peer->chan->dst_type;
> -               lowpan_cb(skb)->chan = peer->chan;
> +               chan = peer->chan;
> +               l2cap_chan_hold(chan);
> +               memcpy(peer_lladdr, peer->lladdr, ETH_ALEN);
> +               *peer_addr = chan->dst;
> +               *peer_addr_type = chan->dst_type;
> +               lowpan_cb(skb)->chan = chan;
> +               lowpan_peer_put(peer);
> +
> +               daddr = peer_lladdr;
>
>                 status = 1;
>         }
> @@ -417,8 +453,13 @@ static int setup_header(struct sk_buff *skb, struct net_device *netdev,
>         lowpan_header_compress(skb, netdev, daddr, dev->netdev->dev_addr);
>
>         err = dev_hard_header(skb, netdev, ETH_P_IPV6, NULL, NULL, 0);
> -       if (err < 0)
> +       if (err < 0) {
> +               if (lowpan_cb(skb)->chan) {
> +                       l2cap_chan_put(lowpan_cb(skb)->chan);
> +                       lowpan_cb(skb)->chan = NULL;
> +               }
>                 return err;
> +       }
>
>         return status;
>  }
> @@ -483,15 +524,23 @@ static int send_mcast_pkt(struct sk_buff *skb, struct net_device *netdev)
>                 dev = lowpan_btle_dev(entry->netdev);
>
>                 list_for_each_entry_rcu(pentry, &dev->peers, list) {
> +                       struct l2cap_chan *chan = pentry->chan;
>                         int ret;
>
>                         local_skb = skb_clone(skb, GFP_ATOMIC);
> +                       if (!local_skb) {
> +                               err = -ENOMEM;
> +                               continue;
> +                       }
> +
> +                       l2cap_chan_hold(chan);
>
>                         BT_DBG("xmit %s to %pMR type %u IP %pI6c chan %p",
>                                netdev->name,
> -                              &pentry->chan->dst, pentry->chan->dst_type,
> -                              &pentry->peer_addr, pentry->chan);
> -                       ret = send_pkt(pentry->chan, local_skb, netdev);
> +                              &chan->dst, chan->dst_type,
> +                              &pentry->peer_addr, chan);
> +                       ret = send_pkt(chan, local_skb, netdev);
> +                       l2cap_chan_put(chan);
>                         if (ret < 0)
>                                 err = ret;
>
> @@ -530,10 +579,14 @@ static netdev_tx_t bt_xmit(struct sk_buff *skb, struct net_device *netdev)
>
>         if (err) {
>                 if (lowpan_cb(skb)->chan) {
> +                       struct l2cap_chan *chan = lowpan_cb(skb)->chan;
> +
>                         BT_DBG("xmit %s to %pMR type %u IP %pI6c chan %p",
>                                netdev->name, &addr, addr_type,
> -                              &lowpan_cb(skb)->addr, lowpan_cb(skb)->chan);
> -                       err = send_pkt(lowpan_cb(skb)->chan, skb, netdev);
> +                              &lowpan_cb(skb)->addr, chan);
> +                       err = send_pkt(chan, skb, netdev);
> +                       l2cap_chan_put(chan);
> +                       lowpan_cb(skb)->chan = NULL;
>                 } else {
>                         err = -ENOENT;
>                 }
> @@ -649,6 +702,7 @@ static struct l2cap_chan *add_peer_chan(struct l2cap_chan *chan,
>         if (!peer)
>                 return NULL;
>
> +       refcount_set(&peer->refcnt, 1);
>         peer->chan = chan;
>
>         baswap((void *)peer->lladdr, &chan->dst);
> @@ -802,8 +856,6 @@ static void chan_close_cb(struct l2cap_chan *chan)
>                                last ? "last " : "1 ", peer);
>                         BT_DBG("chan %p orig refcnt %u", chan,
>                                kref_read(&chan->kref));
> -
> -                       l2cap_chan_put(chan);
>                         break;
>                 }
>         }
> @@ -918,6 +970,7 @@ static int bt_6lowpan_connect(bdaddr_t *addr, u8 dst_type)
>  static int bt_6lowpan_disconnect(struct l2cap_conn *conn, u8 dst_type)
>  {
>         struct lowpan_peer *peer;
> +       struct l2cap_chan *chan;
>
>         BT_DBG("conn %p dst type %u", conn, dst_type);
>
> @@ -925,11 +978,16 @@ static int bt_6lowpan_disconnect(struct l2cap_conn *conn, u8 dst_type)
>         if (!peer)
>                 return -ENOENT;
>
> -       BT_DBG("peer %p chan %p", peer, peer->chan);
> +       chan = peer->chan;
> +       l2cap_chan_hold(chan);
> +
> +       BT_DBG("peer %p chan %p", peer, chan);
> +       lowpan_peer_put(peer);
>
> -       l2cap_chan_lock(peer->chan);
> -       l2cap_chan_close(peer->chan, ENOENT);
> -       l2cap_chan_unlock(peer->chan);
> +       l2cap_chan_lock(chan);
> +       l2cap_chan_close(chan, ENOENT);
> +       l2cap_chan_unlock(chan);
> +       l2cap_chan_put(chan);
>
>         return 0;
>  }
> @@ -1169,6 +1227,7 @@ static ssize_t lowpan_control_write(struct file *fp,
>                         peer = lookup_peer(conn);
>                         if (peer) {
>                                 BT_DBG("6LoWPAN connection already exists");
> +                               lowpan_peer_put(peer);
>                                 return -EALREADY;
>                         }

AI got quite a few issues with this change:

https://sashiko.dev/#/patchset/20260509173745.413473-1-rollkingzzc%40gmail.com

-- 
Luiz Augusto von Dentz

^ permalink raw reply

* [bluez/bluez]
From: BluezTestBot @ 2026-05-11 16:17 UTC (permalink / raw)
  To: linux-bluetooth

  Branch: refs/heads/1090532
  Home:   https://github.com/bluez/bluez

To unsubscribe from these emails, change your notification settings at https://github.com/bluez/bluez/settings/notifications

^ permalink raw reply

* [bluez/bluez] 61f18b: all: Remove more unneeded MIN/MAX macro definition
From: hadess @ 2026-05-11 16:17 UTC (permalink / raw)
  To: linux-bluetooth

  Branch: refs/heads/1092801
  Home:   https://github.com/bluez/bluez
  Commit: 61f18ba7be36b9be5ab6dcdde0384342c13a3074
      https://github.com/bluez/bluez/commit/61f18ba7be36b9be5ab6dcdde0384342c13a3074
  Author: Bastien Nocera <hadess@hadess.net>
  Date:   2026-05-11 (Mon, 11 May 2026)

  Changed paths:
    M lib/bluetooth/hci.c
    M src/main.c
    M src/shared/gatt-server.c

  Log Message:
  -----------
  all: Remove more unneeded MIN/MAX macro definition


  Commit: 98ed10e18895daeb15710d710a0803f6115c20f2
      https://github.com/bluez/bluez/commit/98ed10e18895daeb15710d710a0803f6115c20f2
  Author: Bastien Nocera <hadess@hadess.net>
  Date:   2026-05-11 (Mon, 11 May 2026)

  Changed paths:
    M src/shared/util.h
    M unit/test-util.c

  Log Message:
  -----------
  shared/util: Add helper for "cleanup" variable attribute

Use the widespread "cleanup" variable attribute:
https://gcc.gnu.org/onlinedocs/gcc/Common-Attributes.html#index-cleanup

It is implemented by both GCC and clang on platforms where bluez is
used, and can help reduce memory leaks, while improving readability.

This implements:
- generic cleanup (_cleanup_free_)
- cleanup with specific free function (_cleanup_())
- cleanup for specific types (_cleanup_type_(type))
- cleanup for file descriptors
- capturing a variable before it is freed (so it is only freed in error
  paths for example, _steal_() and _steal_fd())

This commit includes tests which should cover all those new helpers.

See also:
https://systemd.io/CODING_STYLE/#memory-allocation
https://docs.gtk.org/glib/auto-cleanup.html


  Commit: efd84a04d454729f63e8d1066d9dc3b43065d395
      https://github.com/bluez/bluez/commit/efd84a04d454729f63e8d1066d9dc3b43065d395
  Author: Bastien Nocera <hadess@hadess.net>
  Date:   2026-05-11 (Mon, 11 May 2026)

  Changed paths:
    M doc/maintainer-guidelines.rst

  Log Message:
  -----------
  doc: Recommend using _cleanup_ and friends


  Commit: af36f1586544499ac5d4fa267283ea58bdaed9e7
      https://github.com/bluez/bluez/commit/af36f1586544499ac5d4fa267283ea58bdaed9e7
  Author: Bastien Nocera <hadess@hadess.net>
  Date:   2026-05-11 (Mon, 11 May 2026)

  Changed paths:
    M src/main.c

  Log Message:
  -----------
  main: Use _cleanup_() to simplify configuration parsing

Use helpers to simplify temporary string usage, and cleanup in error
paths.


  Commit: 6c98d5b5189da2de46d50c8cc7f9bee0ae56c259
      https://github.com/bluez/bluez/commit/6c98d5b5189da2de46d50c8cc7f9bee0ae56c259
  Author: Bastien Nocera <hadess@hadess.net>
  Date:   2026-05-11 (Mon, 11 May 2026)

  Changed paths:
    M client/mgmt.c

  Log Message:
  -----------
  client: Use _cleanup_fd_ to simplify urandom access

fd gets auto-closed before exiting the scope.


  Commit: af87aaabc6926eb294244ab8da514bc4b221b344
      https://github.com/bluez/bluez/commit/af87aaabc6926eb294244ab8da514bc4b221b344
  Author: Bastien Nocera <hadess@hadess.net>
  Date:   2026-05-11 (Mon, 11 May 2026)

  Changed paths:
    M tools/btattach.c

  Log Message:
  -----------
  btattach: Use _cleanup_fd_ to simplify error paths

Use _cleanup_fd_ and _steal_fd() to simplify error paths, and only
"steal" the file descriptor on success.


Compare: https://github.com/bluez/bluez/compare/61f18ba7be36%5E...af87aaabc692

To unsubscribe from these emails, change your notification settings at https://github.com/bluez/bluez/settings/notifications

^ permalink raw reply

* Re: [PATCH 1/4] Bluetooth: hci_sync: pin conn across hci_le_create_conn_sync
From: Luiz Augusto von Dentz @ 2026-05-11 14:53 UTC (permalink / raw)
  To: Michael Bommarito
  Cc: Marcel Holtmann, linux-bluetooth, linux-kernel, Mat Martineau,
	netdev, stable, Pauli Virtanen, Aaron Esau
In-Reply-To: <490e228dd02983fb1530fb114d4174148f810261.1778506829.git.michael.bommarito@gmail.com>

Hi Michael,

On Mon, May 11, 2026 at 10:34 AM Michael Bommarito
<michael.bommarito@gmail.com> wrote:
>
> hci_le_create_conn_sync() runs from the cmd_sync workqueue with a
> struct hci_conn pointer it interprets out of the work item's void
> *data argument. The hci_conn_valid() check at function entry is a
> TOCTOU: nothing prevents hci_disconn_complete_evt() (executing on
> hdev->workqueue rx_work) from running between the
> hci_conn_hash_lookup walk in hci_conn_valid() and the body's first
> deref. hci_disconn_complete_evt() -> hci_conn_del() -> hci_conn_cleanup()
> unregisters the device and drops the final kref, which kfrees the
> hci_conn slot. The cmd_sync callback then writes through the freed
> pointer (clear_bit on conn->flags, conn->state, the four
> le_conn_*_interval fields).
>
> A KASAN slab-use-after-free splat in cache kmalloc-8k confirms the
> bug on linux-next tip commit bee6ea30c487 ("Add linux-next specific
> files for 20260421") under UML+KASAN, matching the slab geometry of
> the syzbot trace fixed in commit 035c25007c9e ("Bluetooth: hci_sync:
> Fix UAF in le_read_features_complete").
>
> Follow the reference-pinning pattern from commit 035c25007c9e
> ("Bluetooth: hci_sync: Fix UAF in le_read_features_complete") and
> commit 0beddb0c380b ("Bluetooth: hci_conn: fix potential UAF in
> create_big_sync"): the queue site takes a reference via
> hci_conn_get() so the slot is not freed between
> hci_disconn_complete_evt() retiring the conn and the cmd_sync
> callback / completion handler returning. The completion handler
> drops the reference on every exit path, including the -ECANCELED
> short-circuit.
>
> Introduce a static helper hci_cmd_sync_queue_conn_once() so the
> get/put pair is not open-coded at every queue site. See the
> helper's kerneldoc for the -EEXIST contract.
>
> The hci_conn_valid() check in the callback body is retained: a
> logically-deleted-but-still-referenced conn has stale
> hdev->conn_hash.list state, and continuing to drive a connection
> attempt on it would be a logic bug even though the memory is safe.
>
> Pauli Virtanen posted a series-wide variant of this fix as
> https://lore.kernel.org/linux-bluetooth/e18591f264c50e15917cb8b9e5f9798d9880979d.1762100290.git.pav@iki.fi/
> (PATCH v2 8/8, 2025-11-02). KASAN reproducer captured under
> UML+KASAN (linux-next tip bee6ea30c487).
>
> Fixes: 881559af5f5c ("Bluetooth: hci_sync: Attempt to dequeue connection attempt")
> Cc: stable@vger.kernel.org
> Assisted-by: Claude:claude-opus-4-7
> Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
> ---
>  net/bluetooth/hci_sync.c | 41 ++++++++++++++++++++++++++++++++--------
>  1 file changed, 34 insertions(+), 7 deletions(-)
>
> diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c
> index fd3aacdea512..b20e07474257 100644
> --- a/net/bluetooth/hci_sync.c
> +++ b/net/bluetooth/hci_sync.c
> @@ -786,6 +786,31 @@ int hci_cmd_sync_queue_once(struct hci_dev *hdev, hci_cmd_sync_work_func_t func,
>  }
>  EXPORT_SYMBOL(hci_cmd_sync_queue_once);
>
> +/* Queue an HCI command entry once, pinning a hci_conn for the duration.
> + *
> + * On success, the cmd_sync queue owns one hci_conn_get() reference;
> + * the supplied destroy callback must hci_conn_put() to balance.
> + *
> + * On any failure return (including -EEXIST, where
> + * hci_cmd_sync_queue_once() neither invokes destroy nor consumes the
> + * data pointer because an existing entry already owns the slot), the
> + * helper releases the reference before returning, so callers do not
> + * need to discriminate failure codes to keep the refcount balanced.
> + */
> +static int hci_cmd_sync_queue_conn_once(struct hci_dev *hdev,

Id suggest we dropped the once at the end so just hci_cmd_sync_queue_conn.

> +                                       hci_cmd_sync_work_func_t func,
> +                                       struct hci_conn *conn,
> +                                       hci_cmd_sync_work_destroy_t destroy)
> +{
> +       int err;
> +
> +       err = hci_cmd_sync_queue_once(hdev, func, hci_conn_get(conn), destroy);
> +       if (err)
> +               hci_conn_put(conn);
> +
> +       return err;

Then we incorporate return (err == -EEXIST) ? 0 : err; logic above, so
I don't think any caller should require queuing multiple procedures
for the same conn.

> +}
> +
>  /* Run HCI command:
>   *
>   * - hdev must be running
> @@ -6982,36 +7007,38 @@ static void create_le_conn_complete(struct hci_dev *hdev, void *data, int err)
>         bt_dev_dbg(hdev, "err %d", err);
>
>         if (err == -ECANCELED)
> -               return;
> +               goto done;
>
>         hci_dev_lock(hdev);
>
>         if (!hci_conn_valid(hdev, conn))
> -               goto done;
> +               goto unlock;
>
>         if (!err) {
>                 hci_connect_le_scan_cleanup(conn, 0x00);
> -               goto done;
> +               goto unlock;
>         }
>
>         /* Check if connection is still pending */
>         if (conn != hci_lookup_le_connect(hdev))
> -               goto done;
> +               goto unlock;
>
>         /* Flush to make sure we send create conn cancel command if needed */
>         flush_delayed_work(&conn->le_conn_timeout);
>         hci_conn_failed(conn, bt_status(err));
>
> -done:
> +unlock:
>         hci_dev_unlock(hdev);
> +done:
> +       hci_conn_put(conn);
>  }
>
>  int hci_connect_le_sync(struct hci_dev *hdev, struct hci_conn *conn)
>  {
>         int err;
>
> -       err = hci_cmd_sync_queue_once(hdev, hci_le_create_conn_sync, conn,
> -                                     create_le_conn_complete);
> +       err = hci_cmd_sync_queue_conn_once(hdev, hci_le_create_conn_sync, conn,
> +                                          create_le_conn_complete);
>         return (err == -EEXIST) ? 0 : err;
>  }
>
> --
> 2.53.0
>


-- 
Luiz Augusto von Dentz

^ permalink raw reply

* Re: [PATCH] Bluetooth: btmtk: accept too short WMT FUNC_CTRL events
From: Luiz Augusto von Dentz @ 2026-05-11 14:46 UTC (permalink / raw)
  To: Pauli Virtanen; +Cc: linux-bluetooth, Mikhail Gavrilov
In-Reply-To: <d9fe37aa17175290bc406ccad8ed4cf533dfd8b8.camel@iki.fi>

Hi Pauli,

On Fri, May 8, 2026 at 3:09 PM Pauli Virtanen <pav@iki.fi> wrote:
>
> Hi Luiz,
>
> pe, 2026-04-24 kello 15:38 -0400, Luiz Augusto von Dentz kirjoitti:
> > Hi Pauli, Tristan,
> >
> > On Fri, Apr 24, 2026 at 3:25 PM Pauli Virtanen <pav@iki.fi> wrote:
> > >
> > > MT7925 (USB ID 0e8d:e025) on fw version 20260106153314 sends WMT
> > > FUNC_CTRL events that are missing the status field.
> > >
> > > Prior to commit 006b9943b982 ("Bluetooth: btmtk: validate WMT event SKB
> > > length before struct access") the status was read from out-of-bounds of
> > > SKB data, which usually would result to success with
> > > BTMTK_WMT_ON_UNDONE, although I don't know the intent here.  The bounds
> > > check added in that commit returns with error instead, producing
> > > "Bluetooth: hci0: Failed to send wmt func ctrl (-22)" and makes the
> > > device unusable.
> > >
> > > Fix the regression by interpreting too short packet as status
> > > BTMTK_WMT_ON_UNDONE, which makes the device work normally again.
> > >
> > > Fixes: 006b9943b982 ("Bluetooth: btmtk: validate WMT event SKB length before struct access")
> > > Signed-off-by: Pauli Virtanen <pav@iki.fi>
> > > ---
> > >
> > > Notes:
> > >     AFAICS the commit is not yet pulled and is only in bluetooth-next, so
> > >     maybe this should be just fixup?
> >
> > Yeah, I'll most likely fix it in place and add your Signed-off-by.
>
> Looks like this got pulled to net without this fix, so it's broken now

My bad, will try to expedite a PR to have it fixed asap.

> >
> > >  drivers/bluetooth/btmtk.c | 4 ++--
> > >  1 file changed, 2 insertions(+), 2 deletions(-)
> > >
> > > diff --git a/drivers/bluetooth/btmtk.c b/drivers/bluetooth/btmtk.c
> > > index ab34f1dd42bc..68a32d11e5ec 100644
> > > --- a/drivers/bluetooth/btmtk.c
> > > +++ b/drivers/bluetooth/btmtk.c
> > > @@ -719,8 +719,8 @@ static int btmtk_usb_hci_wmt_sync(struct hci_dev *hdev,
> > >         case BTMTK_WMT_FUNC_CTRL:
> > >                 if (!skb_pull_data(data->evt_skb,
> > >                                    sizeof(wmt_evt_funcc->status))) {
> > > -                       err = -EINVAL;
> > > -                       goto err_free_skb;
> > > +                       status = BTMTK_WMT_ON_UNDONE;
> > > +                       break;
> >
> > This probably means the original change was never tested on real
> > hardware. We likely need input from the MediaTek team on how to handle
> > these events, as I don't think a public spec exists.
> >
> > >                 }
> > >
> > >                 wmt_evt_funcc = (struct btmtk_hci_wmt_evt_funcc *)wmt_evt;
> > > --
> > > 2.53.0
> > >
> >
>
> --
> Pauli Virtanen



-- 
Luiz Augusto von Dentz

^ permalink raw reply

* Re: [PATCH v1 1/6] sdio: Add syntactic sugar to store a pointer in sdio_driver_id
From: Ulf Hansson @ 2026-05-11 14:33 UTC (permalink / raw)
  To: Uwe Kleine-König (The Capable Hub)
  Cc: Ulf Hansson, Christian A. Ehrhardt, linux-mmc, Greg Kroah-Hartman,
	Wolfram Sang, linux-kernel, Marcel Holtmann,
	Luiz Augusto von Dentz, linux-bluetooth, Matthias Brugger,
	AngeloGioacchino Del Regno, linux-mediatek, Ping-Ke Shih,
	linux-wireless, Felix Fietkau, Lorenzo Bianconi, Ryder Lee,
	Shayne Chen, Sean Wang, Brian Norris, Francesco Dolcini,
	Andy Shevchenko
In-Reply-To: <c830049dcfcd99f005e2ff6742aace9341c61f13.1776429984.git.u.kleine-koenig@baylibre.com>

On Fri, 17 Apr 2026 at 15:11, Uwe Kleine-König (The Capable Hub)
<u.kleine-koenig@baylibre.com> wrote:
>
> On all current Linux architectures sizeof(long) == sizeof(void *) and
> this is used a lot through the kernel. For example it enables the usual
> practice to store pointers in sdio_driver_id's .driver_data member.
>
> This works fine, but involves casting and thus isn't type-safe.
> Additionally with the CHERI architecture extension there are machines
> with sizeof(void *) > sizeof(long) for with the traditional approach of
> storing a pointer in .driver_data doesn't work.
>
> By replacing the plain unsigned long .driver_data by an anonymous union,
> most of the casting can be dropped and it yields a working solution for
> CHERI.
>
> All users of struct sdio_driver_id are initialized in a way that is
> compatible with the new definition, so no adaptions are needed there.
>
> Signed-off-by: Uwe Kleine-König (The Capable Hub) <u.kleine-koenig@baylibre.com>

Acked-by: Ulf Hansson <ulf.hansson@linaro.org>

Feel free to take this via whatever tree makes best sense!

Kind regards
Uffe

> ---
>  include/linux/mod_devicetable.h | 5 ++++-
>  1 file changed, 4 insertions(+), 1 deletion(-)
>
> diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h
> index 5b1725fe9707..0eb5d196f5b5 100644
> --- a/include/linux/mod_devicetable.h
> +++ b/include/linux/mod_devicetable.h
> @@ -414,7 +414,10 @@ struct sdio_device_id {
>         __u8    class;                  /* Standard interface or SDIO_ANY_ID */
>         __u16   vendor;                 /* Vendor or SDIO_ANY_ID */
>         __u16   device;                 /* Device ID or SDIO_ANY_ID */
> -       kernel_ulong_t driver_data;     /* Data private to the driver */
> +       union {                         /* Data private to the driver */
> +               kernel_ulong_t driver_data;
> +               const void *driver_data_ptr;
> +       };
>  };
>
>  /* SSB core, see drivers/ssb/ */
> --
> 2.47.3
>

^ permalink raw reply

* [PATCH 4/4] Bluetooth: hci_sync: pin conn across hci_acl_create_conn_sync
From: Michael Bommarito @ 2026-05-11 14:34 UTC (permalink / raw)
  To: Marcel Holtmann, Luiz Augusto von Dentz, Jonas Dreßler,
	linux-bluetooth, linux-kernel
  Cc: Mat Martineau, netdev, stable, Pauli Virtanen, Aaron Esau,
	Michael Bommarito
In-Reply-To: <cover.1778506829.git.michael.bommarito@gmail.com>

hci_acl_create_conn_sync() shares the TOCTOU pattern with the
three sibling cmd_sync callbacks just fixed: the work item's
void *data is interpreted as a struct hci_conn pointer, validated
with hci_conn_valid() at entry, then immediately written
(conn->state, conn->out, conn->role, conn->attempt++) followed by
a memcpy(conn->dev_class, ie->data.dev_class, 3). If the TOCTOU
race fires the memcpy lands on a freed slot; the three dev_class
bytes are sourced from a remote BR/EDR inquiry response, so a
successful exploit can land attacker-chosen bytes into the heap
object that reused conn's slot.

A KASAN slab-use-after-free splat in cache kmalloc-8k at
conn->state confirms the bug on linux-next tip commit bee6ea30c487
("Add linux-next specific files for 20260421") with the synthetic
harness driving the conn->state write.

The existing queue site at hci_connect_acl_sync() passed a NULL
destroy callback, so the conn was never pinned for the cmd_sync
workqueue dispatch. Introduce create_acl_conn_complete() to balance
the conn pin and convert the queue site to the
hci_cmd_sync_queue_conn_once() helper. The dequeue-on-cancel path
in hci_cancel_connect_sync() now looks up the entry with the same
destroy callback, keeping the hci_cmd_sync_lookup_entry() triple
match consistent.

Prior art: Pauli Virtanen's PATCH v2 8/8 at
https://lore.kernel.org/linux-bluetooth/e18591f264c50e15917cb8b9e5f9798d9880979d.1762100290.git.pav@iki.fi/.

Fixes: 45340097ce6e ("Bluetooth: hci_conn: Only do ACL connections sequentially")
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
---
 net/bluetooth/hci_sync.c | 16 +++++++++++++---
 1 file changed, 13 insertions(+), 3 deletions(-)

diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c
index 47ce9ba63fe2..9a133de16f63 100644
--- a/net/bluetooth/hci_sync.c
+++ b/net/bluetooth/hci_sync.c
@@ -6994,12 +6994,21 @@ static int hci_acl_create_conn_sync(struct hci_dev *hdev, void *data)
 					conn->conn_timeout, NULL);
 }
 
+static void create_acl_conn_complete(struct hci_dev *hdev, void *data, int err)
+{
+	struct hci_conn *conn = data;
+
+	bt_dev_dbg(hdev, "err %d", err);
+
+	hci_conn_put(conn);
+}
+
 int hci_connect_acl_sync(struct hci_dev *hdev, struct hci_conn *conn)
 {
 	int err;
 
-	err = hci_cmd_sync_queue_once(hdev, hci_acl_create_conn_sync, conn,
-				      NULL);
+	err = hci_cmd_sync_queue_conn_once(hdev, hci_acl_create_conn_sync, conn,
+					   create_acl_conn_complete);
 	return (err == -EEXIST) ? 0 : err;
 }
 
@@ -7054,7 +7063,8 @@ int hci_cancel_connect_sync(struct hci_dev *hdev, struct hci_conn *conn)
 	case ACL_LINK:
 		return !hci_cmd_sync_dequeue_once(hdev,
 						  hci_acl_create_conn_sync,
-						  conn, NULL);
+						  conn,
+						  create_acl_conn_complete);
 	case LE_LINK:
 		return !hci_cmd_sync_dequeue_once(hdev, hci_le_create_conn_sync,
 						  conn, create_le_conn_complete);
-- 
2.53.0


^ permalink raw reply related

* [PATCH 3/4] Bluetooth: hci_sync: pin conn across hci_le_big_create_sync
From: Michael Bommarito @ 2026-05-11 14:34 UTC (permalink / raw)
  To: Marcel Holtmann, Luiz Augusto von Dentz, linux-bluetooth,
	linux-kernel
  Cc: Mat Martineau, netdev, stable, Pauli Virtanen, Aaron Esau,
	Michael Bommarito
In-Reply-To: <cover.1778506829.git.michael.bommarito@gmail.com>

hci_le_big_create_sync() interprets its void *data argument as a
struct hci_conn pointer and dereferences conn->iso_qos,
conn->sync_handle, conn->num_bis, conn->bis, and conn->conn_timeout
after the entry hci_conn_valid() check. As with the sibling
cmd_sync callbacks, hci_disconn_complete_evt() can retire the conn
between the validity check and the body's first deref, and the
blocking wait for HCI_EVT_LE_BIG_SYNC_ESTABLISHED extends the
race window to seconds.

A KASAN slab-use-after-free splat in cache kmalloc-8k at conn->flags
(set_bit(HCI_CONN_CREATE_BIG_SYNC, &conn->flags)) confirms the bug
on linux-next tip commit bee6ea30c487 ("Add linux-next specific
files for 20260421").

Convert hci_connect_big_sync() to the hci_cmd_sync_queue_conn_once()
helper and balance the conn pin in create_big_complete()'s
-ECANCELED short-circuit. Promote create_big_complete()'s
hci_conn_valid() + clear_bit() pair to run under hci_dev_lock so
that hci_disconn_complete_evt() cannot remove conn from
hdev->conn_hash.list between the check and the write.

Prior art: Pauli Virtanen's PATCH v2 8/8 at
https://lore.kernel.org/linux-bluetooth/e18591f264c50e15917cb8b9e5f9798d9880979d.1762100290.git.pav@iki.fi/.

Fixes: 024421cf3992 ("Bluetooth: hci_conn: Fix not setting timeout for BIG Create Sync")
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
---
 net/bluetooth/hci_sync.c | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c
index 43779375209b..47ce9ba63fe2 100644
--- a/net/bluetooth/hci_sync.c
+++ b/net/bluetooth/hci_sync.c
@@ -7265,10 +7265,16 @@ static void create_big_complete(struct hci_dev *hdev, void *data, int err)
 	bt_dev_dbg(hdev, "err %d", err);
 
 	if (err == -ECANCELED)
-		return;
+		goto done;
+
+	hci_dev_lock(hdev);
 
 	if (hci_conn_valid(hdev, conn))
 		clear_bit(HCI_CONN_CREATE_BIG_SYNC, &conn->flags);
+
+	hci_dev_unlock(hdev);
+done:
+	hci_conn_put(conn);
 }
 
 static int hci_le_big_create_sync(struct hci_dev *hdev, void *data)
@@ -7320,8 +7326,8 @@ int hci_connect_big_sync(struct hci_dev *hdev, struct hci_conn *conn)
 {
 	int err;
 
-	err = hci_cmd_sync_queue_once(hdev, hci_le_big_create_sync, conn,
-				      create_big_complete);
+	err = hci_cmd_sync_queue_conn_once(hdev, hci_le_big_create_sync, conn,
+					   create_big_complete);
 	return (err == -EEXIST) ? 0 : err;
 }
 
-- 
2.53.0


^ permalink raw reply related

* [PATCH 2/4] Bluetooth: hci_sync: pin conn across hci_le_pa_create_sync
From: Michael Bommarito @ 2026-05-11 14:34 UTC (permalink / raw)
  To: Marcel Holtmann, Luiz Augusto von Dentz, linux-bluetooth,
	linux-kernel
  Cc: Mat Martineau, netdev, stable, Pauli Virtanen, Aaron Esau,
	Michael Bommarito
In-Reply-To: <cover.1778506829.git.michael.bommarito@gmail.com>

hci_le_pa_create_sync() exhibits the same TOCTOU pattern as
hci_le_create_conn_sync(): the cmd_sync callback receives a struct
hci_conn pointer via void *data, calls hci_conn_valid() at entry,
and then dereferences conn->sync_handle, sets a bit on conn->flags,
reads conn->dst / conn->dst_type / conn->iso_qos / conn->sid /
conn->conn_timeout, and blocks waiting for HCI_EV_LE_PA_SYNC_ESTABLISHED.
The wait can run for conn->conn_timeout milliseconds (typically
multiple seconds for periodic-advertising-sync), giving
hci_disconn_complete_evt() a wide window to retire the conn out
from under the callback.

A KASAN slab-use-after-free splat ("Read of size 2 at addr ... The
buggy address is located 52 bytes inside of freed 8192-byte
region", cache kmalloc-8k) confirms the bug on linux-next tip
commit bee6ea30c487 ("Add linux-next specific files for 20260421").
Offset 52 corresponds to conn->sync_handle.

Convert hci_connect_pa_sync() to the hci_cmd_sync_queue_conn_once()
helper introduced in the previous patch, and balance the conn pin
in create_pa_complete()'s -ECANCELED short-circuit.

Prior art: Pauli Virtanen's PATCH v2 8/8 at
https://lore.kernel.org/linux-bluetooth/e18591f264c50e15917cb8b9e5f9798d9880979d.1762100290.git.pav@iki.fi/.

Fixes: 6d0417e4e1cf ("Bluetooth: hci_conn: Fix not setting conn_timeout for Broadcast Receiver")
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
---
 net/bluetooth/hci_sync.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c
index b20e07474257..43779375209b 100644
--- a/net/bluetooth/hci_sync.c
+++ b/net/bluetooth/hci_sync.c
@@ -7089,7 +7089,7 @@ static void create_pa_complete(struct hci_dev *hdev, void *data, int err)
 	bt_dev_dbg(hdev, "err %d", err);
 
 	if (err == -ECANCELED)
-		return;
+		goto done;
 
 	hci_dev_lock(hdev);
 
@@ -7113,6 +7113,8 @@ static void create_pa_complete(struct hci_dev *hdev, void *data, int err)
 
 unlock:
 	hci_dev_unlock(hdev);
+done:
+	hci_conn_put(conn);
 }
 
 static int hci_le_past_params_sync(struct hci_dev *hdev, struct hci_conn *conn,
@@ -7251,8 +7253,8 @@ int hci_connect_pa_sync(struct hci_dev *hdev, struct hci_conn *conn)
 {
 	int err;
 
-	err = hci_cmd_sync_queue_once(hdev, hci_le_pa_create_sync, conn,
-				      create_pa_complete);
+	err = hci_cmd_sync_queue_conn_once(hdev, hci_le_pa_create_sync, conn,
+					   create_pa_complete);
 	return (err == -EEXIST) ? 0 : err;
 }
 
-- 
2.53.0


^ permalink raw reply related

* [PATCH 1/4] Bluetooth: hci_sync: pin conn across hci_le_create_conn_sync
From: Michael Bommarito @ 2026-05-11 14:34 UTC (permalink / raw)
  To: Marcel Holtmann, Luiz Augusto von Dentz, linux-bluetooth,
	linux-kernel
  Cc: Mat Martineau, netdev, stable, Pauli Virtanen, Aaron Esau,
	Michael Bommarito
In-Reply-To: <cover.1778506829.git.michael.bommarito@gmail.com>

hci_le_create_conn_sync() runs from the cmd_sync workqueue with a
struct hci_conn pointer it interprets out of the work item's void
*data argument. The hci_conn_valid() check at function entry is a
TOCTOU: nothing prevents hci_disconn_complete_evt() (executing on
hdev->workqueue rx_work) from running between the
hci_conn_hash_lookup walk in hci_conn_valid() and the body's first
deref. hci_disconn_complete_evt() -> hci_conn_del() -> hci_conn_cleanup()
unregisters the device and drops the final kref, which kfrees the
hci_conn slot. The cmd_sync callback then writes through the freed
pointer (clear_bit on conn->flags, conn->state, the four
le_conn_*_interval fields).

A KASAN slab-use-after-free splat in cache kmalloc-8k confirms the
bug on linux-next tip commit bee6ea30c487 ("Add linux-next specific
files for 20260421") under UML+KASAN, matching the slab geometry of
the syzbot trace fixed in commit 035c25007c9e ("Bluetooth: hci_sync:
Fix UAF in le_read_features_complete").

Follow the reference-pinning pattern from commit 035c25007c9e
("Bluetooth: hci_sync: Fix UAF in le_read_features_complete") and
commit 0beddb0c380b ("Bluetooth: hci_conn: fix potential UAF in
create_big_sync"): the queue site takes a reference via
hci_conn_get() so the slot is not freed between
hci_disconn_complete_evt() retiring the conn and the cmd_sync
callback / completion handler returning. The completion handler
drops the reference on every exit path, including the -ECANCELED
short-circuit.

Introduce a static helper hci_cmd_sync_queue_conn_once() so the
get/put pair is not open-coded at every queue site. See the
helper's kerneldoc for the -EEXIST contract.

The hci_conn_valid() check in the callback body is retained: a
logically-deleted-but-still-referenced conn has stale
hdev->conn_hash.list state, and continuing to drive a connection
attempt on it would be a logic bug even though the memory is safe.

Pauli Virtanen posted a series-wide variant of this fix as
https://lore.kernel.org/linux-bluetooth/e18591f264c50e15917cb8b9e5f9798d9880979d.1762100290.git.pav@iki.fi/
(PATCH v2 8/8, 2025-11-02). KASAN reproducer captured under
UML+KASAN (linux-next tip bee6ea30c487).

Fixes: 881559af5f5c ("Bluetooth: hci_sync: Attempt to dequeue connection attempt")
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
---
 net/bluetooth/hci_sync.c | 41 ++++++++++++++++++++++++++++++++--------
 1 file changed, 34 insertions(+), 7 deletions(-)

diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c
index fd3aacdea512..b20e07474257 100644
--- a/net/bluetooth/hci_sync.c
+++ b/net/bluetooth/hci_sync.c
@@ -786,6 +786,31 @@ int hci_cmd_sync_queue_once(struct hci_dev *hdev, hci_cmd_sync_work_func_t func,
 }
 EXPORT_SYMBOL(hci_cmd_sync_queue_once);
 
+/* Queue an HCI command entry once, pinning a hci_conn for the duration.
+ *
+ * On success, the cmd_sync queue owns one hci_conn_get() reference;
+ * the supplied destroy callback must hci_conn_put() to balance.
+ *
+ * On any failure return (including -EEXIST, where
+ * hci_cmd_sync_queue_once() neither invokes destroy nor consumes the
+ * data pointer because an existing entry already owns the slot), the
+ * helper releases the reference before returning, so callers do not
+ * need to discriminate failure codes to keep the refcount balanced.
+ */
+static int hci_cmd_sync_queue_conn_once(struct hci_dev *hdev,
+					hci_cmd_sync_work_func_t func,
+					struct hci_conn *conn,
+					hci_cmd_sync_work_destroy_t destroy)
+{
+	int err;
+
+	err = hci_cmd_sync_queue_once(hdev, func, hci_conn_get(conn), destroy);
+	if (err)
+		hci_conn_put(conn);
+
+	return err;
+}
+
 /* Run HCI command:
  *
  * - hdev must be running
@@ -6982,36 +7007,38 @@ static void create_le_conn_complete(struct hci_dev *hdev, void *data, int err)
 	bt_dev_dbg(hdev, "err %d", err);
 
 	if (err == -ECANCELED)
-		return;
+		goto done;
 
 	hci_dev_lock(hdev);
 
 	if (!hci_conn_valid(hdev, conn))
-		goto done;
+		goto unlock;
 
 	if (!err) {
 		hci_connect_le_scan_cleanup(conn, 0x00);
-		goto done;
+		goto unlock;
 	}
 
 	/* Check if connection is still pending */
 	if (conn != hci_lookup_le_connect(hdev))
-		goto done;
+		goto unlock;
 
 	/* Flush to make sure we send create conn cancel command if needed */
 	flush_delayed_work(&conn->le_conn_timeout);
 	hci_conn_failed(conn, bt_status(err));
 
-done:
+unlock:
 	hci_dev_unlock(hdev);
+done:
+	hci_conn_put(conn);
 }
 
 int hci_connect_le_sync(struct hci_dev *hdev, struct hci_conn *conn)
 {
 	int err;
 
-	err = hci_cmd_sync_queue_once(hdev, hci_le_create_conn_sync, conn,
-				      create_le_conn_complete);
+	err = hci_cmd_sync_queue_conn_once(hdev, hci_le_create_conn_sync, conn,
+					   create_le_conn_complete);
 	return (err == -EEXIST) ? 0 : err;
 }
 
-- 
2.53.0


^ permalink raw reply related


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox