Linux bluetooth development
 help / color / mirror / Atom feed
* [PATCH v5 1/1] Bluetooth: L2CAP: Fix use-after-free in l2cap_sock_new_connection_cb()
From: Siwei Zhang @ 2026-05-20 16:20 UTC (permalink / raw)
  To: Marcel Holtmann, Luiz Augusto von Dentz; +Cc: linux-bluetooth, Siwei Zhang
In-Reply-To: <20260520162030.2842543-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.

Allocate the channel outside the func to prevent this.

Fixes: 8ffb929098a5 ("Bluetooth: Remove parent socket usage from l2cap_core.c")
Cc: stable@kernel.org
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Siwei Zhang <oss@fourdim.xyz>
---
 include/net/bluetooth/l2cap.h |  8 +++--
 net/bluetooth/6lowpan.c       | 14 ++++-----
 net/bluetooth/l2cap_core.c    | 58 ++++++++++++++++++++++++++++-------
 net/bluetooth/l2cap_sock.c    | 48 +++++++++++++++++------------
 net/bluetooth/smp.c           | 13 +++-----
 5 files changed, 91 insertions(+), 50 deletions(-)

diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
index 5172afee5494..f7a11e6431f0 100644
--- a/include/net/bluetooth/l2cap.h
+++ b/include/net/bluetooth/l2cap.h
@@ -619,7 +619,8 @@ struct l2cap_chan {
 struct l2cap_ops {
 	char			*name;
 
-	struct l2cap_chan	*(*new_connection) (struct l2cap_chan *chan);
+	int			(*new_connection)(struct l2cap_chan *chan,
+						  struct l2cap_chan *new_chan);
 	int			(*recv) (struct l2cap_chan * chan,
 					 struct sk_buff *skb);
 	void			(*teardown) (struct l2cap_chan *chan, int err);
@@ -883,9 +884,10 @@ static inline __u16 __next_seq(struct l2cap_chan *chan, __u16 seq)
 	return (seq + 1) % (chan->tx_win_max + 1);
 }
 
-static inline struct l2cap_chan *l2cap_chan_no_new_connection(struct l2cap_chan *chan)
+static inline int l2cap_chan_no_new_connection(struct l2cap_chan *chan,
+					       struct l2cap_chan *new_chan)
 {
-	return NULL;
+	return -EOPNOTSUPP;
 }
 
 static inline int l2cap_chan_no_recv(struct l2cap_chan *chan, struct sk_buff *skb)
diff --git a/net/bluetooth/6lowpan.c b/net/bluetooth/6lowpan.c
index 23a229ab6a33..286c0b45055b 100644
--- a/net/bluetooth/6lowpan.c
+++ b/net/bluetooth/6lowpan.c
@@ -743,19 +743,19 @@ static inline void chan_ready_cb(struct l2cap_chan *chan)
 	ifup(dev->netdev);
 }
 
-static inline struct l2cap_chan *chan_new_conn_cb(struct l2cap_chan *pchan)
+static inline int chan_new_conn_cb(struct l2cap_chan *pchan,
+				   struct l2cap_chan *chan)
 {
-	struct l2cap_chan *chan;
-
-	chan = chan_create();
-	if (!chan)
-		return NULL;
+	l2cap_chan_set_defaults(chan);
 
+	chan->chan_type = L2CAP_CHAN_CONN_ORIENTED;
+	chan->mode = L2CAP_MODE_LE_FLOWCTL;
+	chan->imtu = 1280;
 	chan->ops = pchan->ops;
 
 	BT_DBG("chan %p pchan %p", chan, pchan);
 
-	return chan;
+	return 0;
 }
 
 static void unregister_dev(struct lowpan_btle_dev *dev)
diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index fdccd62ccca8..505f32034971 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -4051,10 +4051,16 @@ static void l2cap_connect(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd,
 		goto response;
 	}
 
-	chan = pchan->ops->new_connection(pchan);
+	chan = l2cap_chan_create();
 	if (!chan)
 		goto response;
 
+	if (pchan->ops->new_connection(pchan, chan) < 0) {
+		l2cap_chan_put(chan);
+		chan = NULL;
+		goto response;
+	}
+
 	/* For certain devices (ex: HID mouse), support for authentication,
 	 * pairing and bonding is optional. For such devices, inorder to avoid
 	 * the ACL alive for too long after L2CAP disconnection, reset the ACL
@@ -4132,6 +4138,10 @@ static void l2cap_connect(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd,
 		chan->num_conf_req++;
 	}
 
+	/* Drop our local ref; __l2cap_chan_add() pinned chan via the conn list. */
+	if (chan)
+		l2cap_chan_put(chan);
+
 	l2cap_chan_unlock(pchan);
 	l2cap_chan_put(pchan);
 }
@@ -4881,6 +4891,7 @@ static int l2cap_le_connect_req(struct l2cap_conn *conn,
 	struct l2cap_le_conn_rsp rsp;
 	struct l2cap_chan *chan, *pchan;
 	u16 dcid, scid, credits, mtu, mps;
+	u16 rsp_mtu, rsp_mps;
 	__le16 psm;
 	u8 result;
 
@@ -4893,6 +4904,8 @@ static int l2cap_le_connect_req(struct l2cap_conn *conn,
 	psm  = req->psm;
 	dcid = 0;
 	credits = 0;
+	rsp_mtu = 0;
+	rsp_mps = 0;
 
 	if (mtu < 23 || mps < 23)
 		return -EPROTO;
@@ -4953,12 +4966,19 @@ static int l2cap_le_connect_req(struct l2cap_conn *conn,
 		goto response_unlock;
 	}
 
-	chan = pchan->ops->new_connection(pchan);
+	chan = l2cap_chan_create();
 	if (!chan) {
 		result = L2CAP_CR_LE_NO_MEM;
 		goto response_unlock;
 	}
 
+	if (pchan->ops->new_connection(pchan, chan) < 0) {
+		l2cap_chan_put(chan);
+		chan = NULL;
+		result = L2CAP_CR_LE_NO_MEM;
+		goto response_unlock;
+	}
+
 	bacpy(&chan->src, &conn->hcon->src);
 	bacpy(&chan->dst, &conn->hcon->dst);
 	chan->src_type = bdaddr_src_type(conn->hcon);
@@ -4974,6 +4994,8 @@ static int l2cap_le_connect_req(struct l2cap_conn *conn,
 
 	dcid = chan->scid;
 	credits = chan->rx_credits;
+	rsp_mtu = chan->imtu;
+	rsp_mps = chan->mps;
 
 	__set_chan_timer(chan, chan->ops->get_sndtimeo(chan));
 
@@ -4993,6 +5015,9 @@ static int l2cap_le_connect_req(struct l2cap_conn *conn,
 		result = L2CAP_CR_LE_SUCCESS;
 	}
 
+	/* Drop our local ref; __l2cap_chan_add() pinned chan via the conn list. */
+	l2cap_chan_put(chan);
+
 response_unlock:
 	l2cap_chan_unlock(pchan);
 	l2cap_chan_put(pchan);
@@ -5001,13 +5026,8 @@ static int l2cap_le_connect_req(struct l2cap_conn *conn,
 		return 0;
 
 response:
-	if (chan) {
-		rsp.mtu = cpu_to_le16(chan->imtu);
-		rsp.mps = cpu_to_le16(chan->mps);
-	} else {
-		rsp.mtu = 0;
-		rsp.mps = 0;
-	}
+	rsp.mtu = cpu_to_le16(rsp_mtu);
+	rsp.mps = cpu_to_le16(rsp_mps);
 
 	rsp.dcid    = cpu_to_le16(dcid);
 	rsp.credits = cpu_to_le16(credits);
@@ -5177,12 +5197,18 @@ static inline int l2cap_ecred_conn_req(struct l2cap_conn *conn,
 			continue;
 		}
 
-		chan = pchan->ops->new_connection(pchan);
+		chan = l2cap_chan_create();
 		if (!chan) {
 			result = L2CAP_CR_LE_NO_MEM;
 			continue;
 		}
 
+		if (pchan->ops->new_connection(pchan, chan) < 0) {
+			l2cap_chan_put(chan);
+			result = L2CAP_CR_LE_NO_MEM;
+			continue;
+		}
+
 		bacpy(&chan->src, &conn->hcon->src);
 		bacpy(&chan->dst, &conn->hcon->dst);
 		chan->src_type = bdaddr_src_type(conn->hcon);
@@ -5217,6 +5243,9 @@ static inline int l2cap_ecred_conn_req(struct l2cap_conn *conn,
 		} else {
 			l2cap_chan_ready(chan);
 		}
+
+		/* Drop our local ref; __l2cap_chan_add() pinned chan via the conn list. */
+		l2cap_chan_put(chan);
 	}
 
 unlock:
@@ -7399,7 +7428,11 @@ static void l2cap_connect_cfm(struct hci_conn *hcon, u8 status)
 			goto next;
 
 		l2cap_chan_lock(pchan);
-		chan = pchan->ops->new_connection(pchan);
+		chan = l2cap_chan_create();
+		if (chan && pchan->ops->new_connection(pchan, chan) < 0) {
+			l2cap_chan_put(chan);
+			chan = NULL;
+		}
 		if (chan) {
 			bacpy(&chan->src, &hcon->src);
 			bacpy(&chan->dst, &hcon->dst);
@@ -7407,6 +7440,9 @@ static void l2cap_connect_cfm(struct hci_conn *hcon, u8 status)
 			chan->dst_type = dst_type;
 
 			__l2cap_chan_add(conn, chan);
+
+			/* Drop our local ref; __l2cap_chan_add() pinned chan via the conn list. */
+			l2cap_chan_put(chan);
 		}
 
 		l2cap_chan_unlock(pchan);
diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
index dede550d6031..598f24c8f704 100644
--- a/net/bluetooth/l2cap_sock.c
+++ b/net/bluetooth/l2cap_sock.c
@@ -46,7 +46,8 @@ static struct bt_sock_list l2cap_sk_list = {
 static const struct proto_ops l2cap_sock_ops;
 static void l2cap_sock_init(struct sock *sk, struct sock *parent);
 static struct sock *l2cap_sock_alloc(struct net *net, struct socket *sock,
-				     int proto, gfp_t prio, int kern);
+				     int proto, gfp_t prio, int kern,
+				     struct l2cap_chan *chan);
 static void l2cap_sock_cleanup_listen(struct sock *parent);
 
 bool l2cap_is_socket(struct socket *sock)
@@ -1507,12 +1508,13 @@ static void l2cap_sock_cleanup_listen(struct sock *parent)
 	}
 }
 
-static struct l2cap_chan *l2cap_sock_new_connection_cb(struct l2cap_chan *chan)
+static int l2cap_sock_new_connection_cb(struct l2cap_chan *chan,
+					struct l2cap_chan *new_chan)
 {
 	struct sock *sk, *parent = chan->data;
 
 	if (!parent)
-		return NULL;
+		return -EINVAL;
 
 	lock_sock(parent);
 
@@ -1520,15 +1522,15 @@ static struct l2cap_chan *l2cap_sock_new_connection_cb(struct l2cap_chan *chan)
 	if (sk_acceptq_is_full(parent)) {
 		BT_DBG("backlog full %d", parent->sk_ack_backlog);
 		release_sock(parent);
-		return NULL;
+		return -ENOBUFS;
 	}
 
 	sk = l2cap_sock_alloc(sock_net(parent), NULL, BTPROTO_L2CAP,
-			      GFP_ATOMIC, 0);
+			      GFP_ATOMIC, 0, new_chan);
 	if (!sk) {
 		release_sock(parent);
-		return NULL;
-        }
+		return -ENOMEM;
+	}
 
 	bt_sock_reclassify_lock(sk, BTPROTO_L2CAP);
 
@@ -1538,7 +1540,7 @@ static struct l2cap_chan *l2cap_sock_new_connection_cb(struct l2cap_chan *chan)
 
 	release_sock(parent);
 
-	return l2cap_pi(sk)->chan;
+	return 0;
 }
 
 static int l2cap_sock_recv_cb(struct l2cap_chan *chan, struct sk_buff *skb)
@@ -1939,10 +1941,10 @@ static struct proto l2cap_proto = {
 };
 
 static struct sock *l2cap_sock_alloc(struct net *net, struct socket *sock,
-				     int proto, gfp_t prio, int kern)
+				     int proto, gfp_t prio, int kern,
+				     struct l2cap_chan *chan)
 {
 	struct sock *sk;
-	struct l2cap_chan *chan;
 
 	sk = bt_sock_alloc(net, sock, &l2cap_proto, proto, prio, kern);
 	if (!sk)
@@ -1953,14 +1955,11 @@ static struct sock *l2cap_sock_alloc(struct net *net, struct socket *sock,
 
 	INIT_LIST_HEAD(&l2cap_pi(sk)->rx_busy);
 
-	chan = l2cap_chan_create();
-	if (!chan) {
-		sk_free(sk);
-		if (sock)
-			sock->sk = NULL;
-		return NULL;
-	}
-
+	/* The sock owns two refs on chan, matching the puts in
+	 * l2cap_sock_kill() and l2cap_sock_destruct(). The caller keeps
+	 * its own ref independent of these.
+	 */
+	l2cap_chan_hold(chan);
 	l2cap_chan_hold(chan);
 
 	l2cap_pi(sk)->chan = chan;
@@ -1972,6 +1971,7 @@ static int l2cap_sock_create(struct net *net, struct socket *sock, int protocol,
 			     int kern)
 {
 	struct sock *sk;
+	struct l2cap_chan *chan;
 
 	BT_DBG("sock %p", sock);
 
@@ -1986,10 +1986,18 @@ static int l2cap_sock_create(struct net *net, struct socket *sock, int protocol,
 
 	sock->ops = &l2cap_sock_ops;
 
-	sk = l2cap_sock_alloc(net, sock, protocol, GFP_ATOMIC, kern);
-	if (!sk)
+	chan = l2cap_chan_create();
+	if (!chan)
 		return -ENOMEM;
 
+	sk = l2cap_sock_alloc(net, sock, protocol, GFP_ATOMIC, kern, chan);
+	if (!sk) {
+		l2cap_chan_put(chan);
+		return -ENOMEM;
+	}
+	/* Sock has taken its own refs on chan; drop the chan_create() ref. */
+	l2cap_chan_put(chan);
+
 	l2cap_sock_init(sk, NULL);
 	bt_sock_link(&l2cap_sk_list, sk);
 	return 0;
diff --git a/net/bluetooth/smp.c b/net/bluetooth/smp.c
index 1739c1989dbd..25cb5dc580bf 100644
--- a/net/bluetooth/smp.c
+++ b/net/bluetooth/smp.c
@@ -3204,16 +3204,11 @@ static const struct l2cap_ops smp_chan_ops = {
 	.get_sndtimeo		= l2cap_chan_no_get_sndtimeo,
 };
 
-static inline struct l2cap_chan *smp_new_conn_cb(struct l2cap_chan *pchan)
+static inline int smp_new_conn_cb(struct l2cap_chan *pchan,
+				  struct l2cap_chan *chan)
 {
-	struct l2cap_chan *chan;
-
 	BT_DBG("pchan %p", pchan);
 
-	chan = l2cap_chan_create();
-	if (!chan)
-		return NULL;
-
 	chan->chan_type	= pchan->chan_type;
 	chan->ops	= &smp_chan_ops;
 	chan->scid	= pchan->scid;
@@ -3229,9 +3224,9 @@ static inline struct l2cap_chan *smp_new_conn_cb(struct l2cap_chan *pchan)
 	 */
 	atomic_set(&chan->nesting, L2CAP_NESTING_SMP);
 
-	BT_DBG("created chan %p", chan);
+	BT_DBG("initialised chan %p", chan);
 
-	return chan;
+	return 0;
 }
 
 static const struct l2cap_ops smp_root_chan_ops = {
-- 
2.54.0


^ permalink raw reply related

* [PATCH BlueZ v1] shared/rap: Add client real-time ranging registration and notification parsing
From: Prathibha Madugonde @ 2026-05-20 16:30 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: luiz.dentz, quic_mohamull, quic_hbandi, quic_anubhavg

From: Prathibha Madugonde <prathibha.madugonde@oss.qualcomm.com>

Read the RAS Features characteristic to determine whether the remote
device supports real-time ranging. If supported, register for real-time
characteristic notifications using the reqtracker for the CS initiator
role.

Parse incoming segmented RAS ranging data notifications by accumulating
segments via iovec and parsing complete subevent headers and CS mode 0-3
step data, including IQ/tone PCT samples, once the last segment arrives.
---
 src/shared/rap.c | 967 ++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 965 insertions(+), 2 deletions(-)

diff --git a/src/shared/rap.c b/src/shared/rap.c
index 145da2060..eabc6d543 100644
--- a/src/shared/rap.c
+++ b/src/shared/rap.c
@@ -29,6 +29,9 @@
 #define DBG(_rap, fmt, ...) \
 	rap_debug(_rap, "%s:%s() " fmt, __FILE__, __func__, ##__VA_ARGS__)
 
+#define SIGN_EXTEND_TO_16(val, bits) \
+	((int16_t)(((val) ^ (1U << ((bits)-1))) - (1U << ((bits)-1))))
+
 #define RAS_UUID16			0x185B
 
 /* Total number of attribute handles reserved for the RAS service */
@@ -43,6 +46,11 @@
 #define RAS_STEP_ABORTED_BIT   0x80/* set step aborted */
 #define RAS_SUBEVENT_HEADER_SIZE 8
 
+#define CS_MODE_ZERO_WIRE_INIT_SIZE 7
+#define CS_MODE_ZERO_WIRE_REF_SIZE 3
+#define CS_MODE_ONE_WIRE_SIZE_MIN 6
+#define CS_MODE_ONE_WIRE_SIZE_MAX 12
+
 enum pct_format {
 	IQ = 0,
 	PHASE = 1,
@@ -134,6 +142,28 @@ static inline void ranging_header_set_pct_format(struct ranging_header *hdr,
 				((format & 0x03) << 6);
 }
 
+static inline uint8_t ranging_header_get_antenna_mask(
+					const struct ranging_header *hdr)
+{
+	if (!hdr)
+		return 0;
+
+	return hdr->antenna_pct & 0x0F;
+}
+
+static inline uint8_t antenna_mask_count_paths(uint8_t antenna_mask)
+{
+	uint8_t count = 0;
+	uint8_t i;
+
+	for (i = 0; i < 4; i++) {
+		if (antenna_mask & (1u << i))
+			count++;
+	}
+
+	return count;
+}
+
 struct ras_subevent_header {
 	uint16_t start_acl_conn_event;
 	uint16_t frequency_compensation;
@@ -149,9 +179,21 @@ struct ras_subevent_header {
 #define RAS_DONE_STATUS_PACK(ranging, subevent) \
 	((uint8_t)(((ranging) & 0x0F) | (((subevent) & 0x0F) << 4)))
 
+#define RAS_DONE_STATUS_UNPACK_RANGING(packed) \
+	((uint8_t)((packed) & 0x0F))
+
+#define RAS_DONE_STATUS_UNPACK_SUBEVENT(packed) \
+	((uint8_t)(((packed) >> 4) & 0x0F))
+
 #define RAS_ABORT_REASON_PACK(ranging, subevent) \
 	((uint8_t)(((ranging) & 0x0F) | (((subevent) & 0x0F) << 4)))
 
+#define RAS_ABORT_REASON_UNPACK_RANGING(packed) \
+	((uint8_t)((packed) & 0x0F))
+
+#define RAS_ABORT_REASON_UNPACK_SUBEVENT(packed) \
+	((uint8_t)(((packed) >> 4) & 0x0F))
+
 struct ras_subevent {
 	struct ras_subevent_header subevent_header;
 	uint8_t subevent_data[];
@@ -206,6 +248,11 @@ struct cstracker {
 	uint16_t last_start_acl_conn_evt_counter;
 	uint16_t last_freq_comp;
 	int8_t last_ref_pwr_lvl;
+
+	/* Client - first segment carries this */
+	struct ranging_header   ranging_header_;
+	/* Client - subsequent segments appended using iovec */
+	struct iovec segment_data;
 };
 
 /* Ranging Service context */
@@ -254,6 +301,7 @@ struct bt_rap {
 	void *debug_data;
 	void *user_data;
 	struct cstracker *resptracker;
+	struct cstracker *reqtracker;
 };
 
 static struct queue *rap_db;
@@ -274,6 +322,28 @@ struct bt_rap_ready {
 	void *data;
 };
 
+typedef void (*rap_notify_t)(struct bt_rap *rap, uint16_t value_handle,
+			const uint8_t *value, uint16_t length,
+			void *user_data);
+
+struct bt_rap_notify {
+	unsigned int id;
+	struct bt_rap *rap;
+	rap_notify_t func;
+	void *user_data;
+};
+
+typedef void (*rap_func_t)(struct bt_rap *rap, bool success,
+			uint8_t att_ecode, const uint8_t *value,
+			uint16_t length, void *user_data);
+
+struct bt_rap_pending {
+	unsigned int id;
+	struct bt_rap *rap;
+	rap_func_t func;
+	void *userdata;
+};
+
 uint16_t default_ras_mtu = 247; /*Section 3.1.2 of RAP 1.0*/
 uint8_t ras_segment_header_size = 1;
 
@@ -542,6 +612,11 @@ static void rap_free(void *data)
 		rap->resptracker = NULL;
 	}
 
+	if (rap->reqtracker) {
+		free(rap->reqtracker);
+		rap->reqtracker = NULL;
+	}
+
 	queue_destroy(rap->notify, free);
 	queue_destroy(rap->pending, NULL);
 	queue_destroy(rap->ready_cbs, rap_ready_free);
@@ -641,6 +716,12 @@ static void cs_tracker_init(struct cstracker *t)
 	t->last_start_acl_conn_evt_counter = 0;
 	t->last_freq_comp = 0;
 	t->last_ref_pwr_lvl = 0;
+
+	/* Initialize ranging header using helper functions */
+	memset(&t->ranging_header_, 0, sizeof(t->ranging_header_));
+	/* Initialize segment accumulator */
+	t->segment_data.iov_base = NULL;
+	t->segment_data.iov_len = 0;
 }
 
 static void ras_features_read_cb(struct gatt_db_attribute *attrib,
@@ -1698,6 +1779,46 @@ static void form_ras_data_with_cs_subevent_result_cont(struct bt_rap *rap,
 		cont->step_data);
 }
 
+static void fill_initiator_data_from_cs_subevent_result_cont(struct bt_rap *rap,
+		const struct rap_ev_cs_subevent_result_cont *cont,
+		uint16_t length)
+{
+	size_t base_len = offsetof(struct rap_ev_cs_subevent_result_cont,
+					step_data);
+	struct cstracker *reqtracker;
+
+	if (!rap || !rap->reqtracker || !cont)
+		return;
+
+	if (length < base_len)
+		return;
+
+	DBG(rap, "Received CS subevent result continue subevent: len=%d",
+		length);
+
+	reqtracker = rap->reqtracker;
+
+}
+
+static void fill_initiator_data_from_cs_subevent_result(struct bt_rap *rap,
+		const struct rap_ev_cs_subevent_result *data,
+		uint16_t length)
+{
+	size_t base_len = offsetof(struct rap_ev_cs_subevent_result,
+					step_data);
+
+	if (!rap || !rap->reqtracker || !data)
+		return;
+
+	/* Defensive check: base header must be present */
+	if (length < base_len)
+		return;
+
+	DBG(rap, "Received CS subevent result subevent: len=%d", length);
+
+
+}
+
 void bt_rap_hci_cs_subevent_result_cont_callback(uint16_t length,
 						const void *param,
 						void *user_data)
@@ -1707,7 +1828,12 @@ void bt_rap_hci_cs_subevent_result_cont_callback(uint16_t length,
 
 	DBG(rap, "Received CS subevent CONT: len=%d", length);
 
-	form_ras_data_with_cs_subevent_result_cont(rap, cont, length);
+	if (rap->resptracker->role == CS_REFLECTOR)
+		form_ras_data_with_cs_subevent_result_cont(rap, cont, length);
+
+	if (rap->reqtracker->role == CS_INITIATOR)
+		fill_initiator_data_from_cs_subevent_result_cont(rap, cont,
+								length);
 }
 
 void bt_rap_hci_cs_subevent_result_callback(uint16_t length,
@@ -1720,7 +1846,11 @@ void bt_rap_hci_cs_subevent_result_callback(uint16_t length,
 	DBG(rap, "Received CS subevent: len=%d", length);
 
 	/* Populate CsProcedureData and send RAS payload */
-	form_ras_data_with_cs_subevent_result(rap, data, length);
+	if (rap->resptracker->role == CS_REFLECTOR)
+		form_ras_data_with_cs_subevent_result(rap, data, length);
+
+	if (rap->reqtracker->role == CS_INITIATOR)
+		fill_initiator_data_from_cs_subevent_result(rap, data, length);
 }
 
 void bt_rap_hci_cs_procedure_enable_complete_callback(uint16_t length,
@@ -1763,6 +1893,7 @@ void bt_rap_hci_cs_config_complete_callback(uint16_t length,
 	const struct rap_ev_cs_config_cmplt *data = param;
 	struct bt_rap *rap = user_data;
 	struct cstracker *resptracker;
+	struct cstracker *reqtracker;
 
 	if (!rap)
 		return;
@@ -1781,6 +1912,19 @@ void bt_rap_hci_cs_config_complete_callback(uint16_t length,
 	resptracker->config_id = data->config_id;
 	resptracker->role = data->role;
 	resptracker->rtt_type = data->rtt_type;
+
+	if (!rap->reqtracker) {
+		reqtracker = new0(struct cstracker, 1);
+		cs_tracker_init(reqtracker);
+		rap->reqtracker = reqtracker;
+	}
+
+	reqtracker = rap->reqtracker;
+
+	/* Basic fields */
+	reqtracker->config_id = data->config_id;
+	reqtracker->role = data->role;
+	reqtracker->rtt_type = data->rtt_type;
 }
 
 struct bt_rap *bt_rap_new(struct gatt_db *ldb, struct gatt_db *rdb)
@@ -1815,6 +1959,821 @@ done:
 	return rap;
 }
 
+static void ras_pending_destroy(void *data)
+{
+	struct bt_rap_pending *pending = data;
+	struct bt_rap *rap = pending->rap;
+
+	if (queue_remove_if(rap->pending, NULL, pending))
+		free(pending);
+}
+
+static void ras_pending_complete(bool success, uint8_t att_ecode,
+				const uint8_t *value, uint16_t length,
+				void *user_data)
+{
+	struct bt_rap_pending *pending = user_data;
+
+	if (pending->func)
+		pending->func(pending->rap, success, att_ecode, value, length,
+					  pending->userdata);
+}
+
+static void rap_read_value(struct bt_rap *rap, uint16_t value_handle,
+			   rap_func_t func, void *user_data)
+{
+	struct bt_rap_pending *pending;
+
+	pending = new0(struct bt_rap_pending, 1);
+	pending->rap = rap;
+	pending->func = func;
+	pending->userdata = user_data;
+
+	pending->id = bt_gatt_client_read_value(rap->client, value_handle,
+						ras_pending_complete, pending,
+						ras_pending_destroy);
+	if (!pending->id) {
+		DBG(rap, "Unable to send Read request");
+		free(pending);
+		return;
+	}
+
+	queue_push_tail(rap->pending, pending);
+}
+
+static void ras_register(uint16_t att_ecode, void *user_data)
+{
+	struct bt_rap_notify *notify = user_data;
+
+	if (att_ecode)
+		DBG(notify->rap, "RAS register failed 0x%04x", att_ecode);
+
+	(void)notify;
+}
+
+static void rap_notify(uint16_t value_handle, const uint8_t *value,
+				uint16_t length, void *user_data)
+{
+	struct bt_rap_notify *notify = user_data;
+
+	if (notify->func)
+		notify->func(notify->rap, value_handle, value, length,
+					 notify->user_data);
+}
+
+static void rap_notify_destroy(void *data)
+{
+	struct bt_rap_notify *notify = data;
+	struct bt_rap *rap = notify->rap;
+
+	if (queue_remove_if(rap->notify, NULL, notify))
+		free(notify);
+}
+
+static unsigned int bt_rap_register_notify(struct bt_rap *rap,
+					uint16_t value_handle,
+					rap_notify_t func,
+					void *user_data)
+{
+	struct bt_rap_notify *notify;
+
+	notify = new0(struct bt_rap_notify, 1);
+	notify->rap = rap;
+	notify->func = func;
+	notify->user_data = user_data;
+
+	DBG(rap, "register for notifications");
+
+	notify->id = bt_gatt_client_register_notify(rap->client,
+					value_handle, ras_register,
+					rap_notify, notify,
+					rap_notify_destroy);
+	if (!notify->id) {
+		DBG(rap, "Unable to register for notifications");
+		free(notify);
+		return 0;
+	}
+
+	queue_push_tail(rap->notify, notify);
+
+	return notify->id;
+}
+
+static inline bool parse_segmentation_header(struct iovec *iov,
+					     struct segmentation_header *s)
+{
+	uint8_t byte;
+
+	if (!util_iov_pull_u8(iov, &byte))
+		return false;
+
+	s->first_segment = (byte & 0x01) ? 1 : 0;
+	s->last_segment = (byte & 0x02) ? 1 : 0;
+	s->rolling_segment_counter = (byte >> 2) & 0x3F;
+
+	return true;
+}
+
+static void parse_i_q_sample(struct iovec *iov, int16_t *i_sample,
+				int16_t *q_sample)
+{
+	uint32_t buffer;
+	uint32_t i12;
+	uint32_t q12;
+
+	if (!util_iov_pull_le24(iov, &buffer)) {
+		*i_sample = 0;
+		*q_sample = 0;
+		return;
+	}
+
+	i12 =  buffer        & 0x0FFFU;   /* bits 0..11 */
+	q12 = (buffer >> 12) & 0x0FFFU;   /* bits 12..23 */
+
+	*i_sample = SIGN_EXTEND_TO_16(i12, 12);
+	*q_sample = SIGN_EXTEND_TO_16(q12, 12);
+}
+
+static size_t get_mode_zero_length(enum cs_role remote_role)
+{
+	return (remote_role == CS_ROLE_INITIATOR) ?
+		CS_MODE_ZERO_WIRE_INIT_SIZE :
+		CS_MODE_ZERO_WIRE_REF_SIZE;
+}
+
+static void parse_mode_zero(struct bt_rap *rap, struct iovec *mode_iov,
+			    enum cs_role remote_role)
+{
+	uint8_t packet_quality;
+	int8_t packet_rssi_dbm;
+	uint8_t packet_ant;
+	uint32_t init_measured_freq_offset = 0;
+
+	if (!util_iov_pull_u8(mode_iov, &packet_quality) ||
+	    !util_iov_pull_u8(mode_iov, (uint8_t *)&packet_rssi_dbm) ||
+	    !util_iov_pull_u8(mode_iov, &packet_ant)) {
+		DBG(rap, "Mode 0: failed to parse common fields");
+		return;
+	}
+
+	if (remote_role == CS_ROLE_INITIATOR) {
+		if (!util_iov_pull_le32(mode_iov, &init_measured_freq_offset)) {
+			DBG(rap, "Mode 0: failed to parse freq offset");
+			return;
+		}
+	}
+
+	if (remote_role == CS_ROLE_INITIATOR) {
+		DBG(rap, "    cs_mode_zero_data (INITIATOR): "
+			"Packet_Quality=%u Packet_RSSI=%d "
+			"Packet_Antenna=%u Measured_Freq_Offset=%u",
+			packet_quality,
+			packet_rssi_dbm,
+			packet_ant,
+			init_measured_freq_offset);
+	} else {
+		DBG(rap, "    cs_mode_zero_data (REFLECTOR): "
+			"Packet_Quality=%u Packet_RSSI=%d "
+			"Packet_Antenna=%u",
+			packet_quality,
+			packet_rssi_dbm,
+			packet_ant);
+	}
+}
+
+static size_t get_mode_one_length(bool include_pct)
+{
+	if (include_pct)
+		return CS_MODE_ONE_WIRE_SIZE_MAX;
+	return CS_MODE_ONE_WIRE_SIZE_MIN;
+}
+
+static void parse_mode_one(struct bt_rap *rap, struct iovec *mode_iov,
+			   enum cs_role remote_role, bool include_pct)
+{
+	uint8_t packet_quality;
+	uint8_t packet_nadm;
+	int8_t packet_rssi_dbm;
+	int16_t time_value;
+	uint8_t packet_ant;
+	int16_t pct1_i = 0, pct1_q = 0;
+	int16_t pct2_i = 0, pct2_q = 0;
+
+	/* Parse fixed fields (6 bytes) using util_iov_pull_* */
+	if (!util_iov_pull_u8(mode_iov, &packet_quality) ||
+	    !util_iov_pull_u8(mode_iov, &packet_nadm) ||
+	    !util_iov_pull_u8(mode_iov, (uint8_t *)&packet_rssi_dbm) ||
+	    !util_iov_pull_le16(mode_iov, (uint16_t *)&time_value) ||
+	    !util_iov_pull_u8(mode_iov, &packet_ant)) {
+		DBG(rap, "Mode 1: failed to parse fixed fields");
+		return;
+	}
+
+	/* Parse optional PCT fields using parse_i_q_sample */
+	if (include_pct) {
+		parse_i_q_sample(mode_iov, &pct1_i, &pct1_q);
+		parse_i_q_sample(mode_iov, &pct2_i, &pct2_q);
+	}
+
+	if (remote_role == CS_ROLE_INITIATOR) {
+		DBG(rap, "    cs_mode_one_data (INITIATOR): "
+			"Packet_Quality=%u Packet_NADM=%u "
+			"Packet_RSSI=%d ToA_ToD_Initiator=0x%4.4x "
+			"Packet_Antenna=%u",
+			packet_quality,
+			packet_nadm,
+			packet_rssi_dbm,
+			time_value,
+			packet_ant);
+		if (include_pct) {
+			DBG(rap, "    cs_mode_one_data (INITIATOR): "
+				"Packet_PCT1(I=0x%3.3x,Q=0x%3.3x) "
+				"Packet_PCT2(I=0x%3.3x,Q=0x%3.3x)",
+				pct1_i, pct1_q,
+				pct2_i, pct2_q);
+		}
+	} else {
+		DBG(rap, "    cs_mode_one_data (REFLECTOR): "
+			"Packet_Quality=%u Packet_NADM=%u "
+			"Packet_RSSI=%d ToD_ToA_Reflector=0x%4.4x  "
+			"Packet_Antenna=%u",
+			packet_quality,
+			packet_nadm,
+			packet_rssi_dbm,
+			time_value,
+			packet_ant);
+		if (include_pct) {
+			DBG(rap, "    cs_mode_one_data (REFLECTOR): "
+				"Packet_PCT1(I=0x%3.3x,Q=0x%3.3x) "
+				"Packet_PCT2(I=0x%3.3x,Q=0x%3.3x)",
+				pct1_i, pct1_q,
+				pct2_i, pct2_q);
+		}
+	}
+}
+
+static size_t get_mode_two_length(uint8_t num_antenna_paths)
+{
+	uint8_t num_tone_data = num_antenna_paths + 1;
+
+	return 1 + (4 * num_tone_data);
+}
+
+static void parse_mode_two(struct bt_rap *rap, struct iovec *mode_iov)
+{
+	uint8_t ant_perm_index;
+	int16_t tone_pct_i[5];
+	int16_t tone_pct_q[5];
+	uint8_t tone_quality[5];
+	uint8_t k;
+	uint8_t max_paths = 5;
+
+	if (!util_iov_pull_u8(mode_iov, &ant_perm_index)) {
+		DBG(rap, "Mode 2: failed to parse ant_perm_index");
+		return;
+	}
+
+	for (k = 0; k < max_paths; k++) {
+		int16_t i_val, q_val;
+
+		if (mode_iov->iov_len < 4) {
+			DBG(rap, "Mode 2: insufficient PCT for "
+				"path %u (rem=%zu)",
+				k, mode_iov->iov_len);
+			break;
+		}
+		parse_i_q_sample(mode_iov, &i_val, &q_val);
+		tone_pct_i[k] = i_val;
+		tone_pct_q[k] = q_val;
+
+		util_iov_pull_u8(mode_iov, &tone_quality[k]);
+		DBG(rap, "tone_quality_indicator : %d",
+			tone_quality[k]);
+		DBG(rap, "[i, q] : %d, %d",
+			tone_pct_i[k],
+			tone_pct_q[k]);
+	}
+
+	DBG(rap, "    cs_mode_two_data: ant_perm_idx=%u",
+		ant_perm_index);
+	for (k = 0; k < max_paths; k++) {
+		DBG(rap, "      tone[%u]: pct(I=0x%3.3x,Q=0x%3.3x) quality=%u",
+			k,
+			tone_pct_i[k],
+			tone_pct_q[k],
+			tone_quality[k]);
+	}
+}
+
+static size_t get_mode_three_length(uint8_t num_antenna_paths, bool include_pct)
+{
+	uint8_t num_tone_data = num_antenna_paths + 1;
+	size_t len;
+
+	/* Mode 1 portion: 6 bytes base + optional 6 bytes PCT */
+	len = 6;
+	if (include_pct)
+		len += 6;
+
+	/* Mode 2 portion: 1 byte antenna index + 4 bytes per tone */
+	len += 1 + (4 * num_tone_data);
+
+	return len;
+}
+
+static void parse_mode_three(struct bt_rap *rap, struct iovec *mode_iov,
+			     enum cs_role remote_role, bool include_pct)
+{
+	uint8_t packet_quality;
+	uint8_t packet_nadm;
+	int8_t packet_rssi_dbm;
+	int16_t time_value;
+	uint8_t packet_ant;
+	int16_t pct1_i = 0, pct1_q = 0;
+	int16_t pct2_i = 0, pct2_q = 0;
+	uint8_t ant_perm_index;
+	int16_t tone_pct_i[5];
+	int16_t tone_pct_q[5];
+	uint8_t tone_quality[5];
+	uint8_t k;
+	uint8_t max_paths = 5;
+
+	/* Parse Mode 1 portion (6 or 12 bytes) */
+	if (!util_iov_pull_u8(mode_iov, &packet_quality) ||
+	    !util_iov_pull_u8(mode_iov, &packet_nadm) ||
+	    !util_iov_pull_u8(mode_iov, (uint8_t *)&packet_rssi_dbm) ||
+	    !util_iov_pull_le16(mode_iov, (uint16_t *)&time_value) ||
+	    !util_iov_pull_u8(mode_iov, &packet_ant)) {
+		DBG(rap, "Mode 3: failed to parse Mode 1 fixed fields");
+		return;
+	}
+
+	/* Parse optional PCT fields using new logic */
+	if (include_pct) {
+		parse_i_q_sample(mode_iov, &pct1_i, &pct1_q);
+		parse_i_q_sample(mode_iov, &pct2_i, &pct2_q);
+	}
+
+	/* Parse Mode 2 portion - antenna permutation index */
+	if (!util_iov_pull_u8(mode_iov, &ant_perm_index)) {
+		DBG(rap, "Mode 3: failed to parse ant_perm_index");
+		return;
+	}
+
+	/* Parse tone paths using new logic */
+	for (k = 0; k < max_paths; k++) {
+		int16_t i_val, q_val;
+
+		if (mode_iov->iov_len < 4) {
+			DBG(rap, "Mode 3: insufficient PCT for "
+				"path %u (rem=%zu)",
+				k, mode_iov->iov_len);
+			break;
+		}
+		parse_i_q_sample(mode_iov, &i_val, &q_val);
+		tone_pct_i[k] = i_val;
+		tone_pct_q[k] = q_val;
+
+		util_iov_pull_u8(mode_iov, &tone_quality[k]);
+		DBG(rap, "tone_quality_indicator : %d",
+			tone_quality[k]);
+		DBG(rap, "[i, q] : %d, %d",
+			tone_pct_i[k],
+			tone_pct_q[k]);
+	}
+
+	if (remote_role == CS_ROLE_INITIATOR) {
+		DBG(rap, "    cs_mode_three_data (INITIATOR): "
+			"Packet_Quality=%u Packet_NADM=%u "
+			"Packet_RSSI=%d ToA_ToD_Initiator=0x%4.4x "
+			"Packet_Antenna=%u",
+			packet_quality,
+			packet_nadm,
+			packet_rssi_dbm,
+			time_value,
+			packet_ant);
+		if (include_pct) {
+			DBG(rap, "    cs_mode_three_data (INITIATOR): "
+				"Packet_PCT1(I=0x%3.3x,Q=0x%3.3x) "
+				"Packet_PCT2(I=0x%3.3x,Q=0x%3.3x)",
+				pct1_i, pct1_q,
+				pct2_i, pct2_q);
+		}
+		DBG(rap, "    cs_mode_three_data (INITIATOR): "
+			"Antenna_Permutation_Index=%u",
+			ant_perm_index);
+		for (k = 0; k < max_paths; k++) {
+			DBG(rap, "      Tone_PCT[%u](I=0x%3.3x,Q=0x%3.3x) "
+				"Tone_Quality_Indicator[%u]=%u",
+				k,
+				tone_pct_i[k],
+				tone_pct_q[k],
+				k,
+				tone_quality[k]);
+		}
+	} else {
+		DBG(rap, "    cs_mode_three_data (REFLECTOR): "
+			"Packet_Quality=%u Packet_NADM=%u "
+			"Packet_RSSI=%d ToD_ToA_Reflector=0x%4.4x "
+			"Packet_Antenna=%u",
+			packet_quality,
+			packet_nadm,
+			packet_rssi_dbm,
+			time_value,
+			packet_ant);
+		if (include_pct) {
+			DBG(rap, "    cs_mode_three_data (REFLECTOR): "
+				"Packet_PCT1(I=0x%3.3x,Q=0x%3.3x) "
+				"Packet_PCT2(I=0x%3.3x,Q=0x%3.3x)",
+				pct1_i, pct1_q,
+				pct2_i, pct2_q);
+		}
+		DBG(rap, "    cs_mode_three_data (REFLECTOR): "
+			"Antenna_Permutation_Index=%u",
+			ant_perm_index);
+		for (k = 0; k < max_paths; k++) {
+			DBG(rap, "      Tone_PCT[%u](I=0x%3.3x,Q=0x%3.3x) "
+				"Tone_Quality_Indicator[%u]=%u",
+				k,
+				tone_pct_i[k],
+				tone_pct_q[k],
+				k,
+				tone_quality[k]);
+		}
+	}
+}
+
+static void parse_ras_data_segments(struct bt_rap *rap,
+			       struct cstracker *reqtracker)
+{
+	const uint8_t *buf;
+	size_t buf_len;
+	size_t offset = 0;
+	uint8_t antenna_mask;
+	uint8_t num_antenna_paths;
+
+	if (!rap || !reqtracker)
+		return;
+
+	DBG(rap, "Complete RAS data received: %zu bytes",
+	    reqtracker->segment_data.iov_len);
+
+	/* Extract num_antenna_paths from ranging header */
+	antenna_mask =
+		ranging_header_get_antenna_mask(&reqtracker->ranging_header_);
+	num_antenna_paths = antenna_mask_count_paths(antenna_mask);
+
+	/* Validate complete RAS data structure before parsing */
+	buf = (const uint8_t *)reqtracker->segment_data.iov_base;
+	buf_len = reqtracker->segment_data.iov_len;
+
+	/* Process all subevents in the accumulated buffer */
+	while (offset + RAS_SUBEVENT_HEADER_SIZE <= buf_len) {
+		struct ras_subevent_header subevt_hdr;
+		const uint8_t *hdr = buf + offset;
+		uint8_t done_byte;
+		uint8_t abort_byte;
+
+		subevt_hdr.start_acl_conn_event = get_le16(hdr);
+		subevt_hdr.frequency_compensation =
+			get_le16(hdr + 2);
+
+		done_byte = hdr[4];
+		subevt_hdr.ranging_done_status =
+			RAS_DONE_STATUS_UNPACK_RANGING(done_byte);
+		subevt_hdr.subevent_done_status =
+			RAS_DONE_STATUS_UNPACK_SUBEVENT(done_byte);
+
+		abort_byte = hdr[5];
+		subevt_hdr.ranging_abort_reason =
+			RAS_ABORT_REASON_UNPACK_RANGING(abort_byte);
+		subevt_hdr.subevent_abort_reason =
+			RAS_ABORT_REASON_UNPACK_SUBEVENT(abort_byte);
+
+		subevt_hdr.reference_power_level = (int8_t)hdr[6];
+		subevt_hdr.num_steps_reported = hdr[7];
+
+		DBG(rap, "Parsed subevent: start_acl=%u "
+			"freq_comp=%d ref_pwr=%d steps=%u",
+		    subevt_hdr.start_acl_conn_event,
+		    subevt_hdr.frequency_compensation,
+		    subevt_hdr.reference_power_level,
+		    subevt_hdr.num_steps_reported);
+
+		offset += RAS_SUBEVENT_HEADER_SIZE;
+
+		/* Parse step data for this subevent */
+		for (uint8_t step_idx = 0;
+		     step_idx < subevt_hdr.num_steps_reported;
+		     step_idx++) {
+			uint8_t mode_byte;
+			bool step_aborted;
+			uint8_t step_mode;
+			size_t step_payload_len = 0;
+			uint8_t rtt_type;
+			bool include_pct;
+			/* Determine remote role */
+			enum cs_role local_role = reqtracker->role;
+			enum cs_role remote_role =
+				(local_role == CS_ROLE_INITIATOR) ?
+				CS_ROLE_REFLECTOR : CS_ROLE_INITIATOR;
+
+			if (offset >= buf_len) {
+				DBG(rap, "Insufficient data for "
+					"step %u", step_idx);
+				break;
+			}
+
+			/* Parse: 1 byte mode + variable payload */
+			mode_byte = buf[offset++];
+			step_aborted = (mode_byte & RAS_STEP_ABORTED_BIT) != 0;
+			step_mode = mode_byte & 0x7F;
+
+			if (step_aborted) {
+				DBG(rap, "  Step %u: mode=%u "
+					"(aborted)",
+					step_idx, step_mode);
+				continue;
+			}
+
+			/* Determine payload length and parse mode data */
+			rtt_type = reqtracker->rtt_type;
+			include_pct = (rtt_type == 0x01 || rtt_type == 0x02);
+
+			switch (step_mode) {
+			case CS_MODE_ZERO:
+				step_payload_len =
+					get_mode_zero_length(remote_role);
+				break;
+			case CS_MODE_ONE:
+				step_payload_len =
+					get_mode_one_length(include_pct);
+				break;
+			case CS_MODE_TWO:
+				step_payload_len =
+					get_mode_two_length(num_antenna_paths);
+				break;
+			case CS_MODE_THREE:
+				step_payload_len =
+					get_mode_three_length(
+						num_antenna_paths,
+						include_pct);
+				break;
+			default:
+				DBG(rap, "  Step %u: unknown mode=%u",
+					step_idx, step_mode);
+				step_payload_len = 0;
+				break;
+			}
+
+			if (offset + step_payload_len > buf_len) {
+				DBG(rap, "Insufficient data for "
+					"step %u payload (need %zu, have %zu)",
+					step_idx, step_payload_len,
+					buf_len - offset);
+				break;
+			}
+
+			DBG(rap, "  Step %u: mode=%u payload_len=%zu",
+			    step_idx, step_mode, step_payload_len);
+
+			/* Parse mode data if payload is present */
+			if (step_payload_len > 0) {
+				const uint8_t *payload_ptr = buf + offset;
+				struct iovec mode_iov;
+
+				mode_iov.iov_base = (void *)payload_ptr;
+				mode_iov.iov_len = step_payload_len;
+
+				switch (step_mode) {
+				case CS_MODE_ZERO:
+					parse_mode_zero(rap, &mode_iov,
+							remote_role);
+					break;
+				case CS_MODE_ONE:
+					parse_mode_one(rap, &mode_iov,
+						remote_role, include_pct);
+					break;
+				case CS_MODE_TWO:
+					parse_mode_two(rap, &mode_iov);
+					break;
+				case CS_MODE_THREE:
+					parse_mode_three(rap, &mode_iov,
+						remote_role, include_pct);
+					break;
+				default:
+					break;
+				}
+
+				/* Skip payload */
+				offset += step_payload_len;
+			}
+		}
+
+		/* Check if this is the last subevent */
+		if (subevt_hdr.subevent_done_status ==
+		    SUBEVENT_DONE_ALL_RESULTS_COMPLETE ||
+		    subevt_hdr.ranging_done_status ==
+		    RANGING_DONE_ALL_RESULTS_COMPLETE) {
+			DBG(rap, "Ranging procedure complete");
+			break;
+		}
+	}
+
+	/* Clean up accumulator */
+	free(reqtracker->segment_data.iov_base);
+	reqtracker->segment_data.iov_base = NULL;
+	reqtracker->segment_data.iov_len = 0;
+}
+
+static bool process_first_segment(struct bt_rap *rap,
+					struct cstracker *reqtracker,
+					struct iovec *iov, bool last_segment)
+{
+	uint16_t counter_config_val;
+	int8_t selected_tx_power;
+	uint8_t antenna_pct;
+
+	if (!util_iov_pull_le16(iov, &counter_config_val) ||
+	    !util_iov_pull_u8(iov, (uint8_t *)&selected_tx_power) ||
+	    !util_iov_pull_u8(iov, &antenna_pct)) {
+		DBG(rap, "First segment too short for ranging header");
+		return false;
+	}
+
+	ranging_header_set_counter(&reqtracker->ranging_header_,
+				counter_config_val & 0x0FFF);
+	ranging_header_set_config_id(&reqtracker->ranging_header_,
+				(counter_config_val >> 12) & 0x0F);
+	reqtracker->ranging_header_.selected_tx_power = selected_tx_power;
+	reqtracker->ranging_header_.antenna_pct = antenna_pct;
+
+	DBG(rap, "First segment: parsed ranging header "
+	    "(counter=%u, config_id=%u, tx_pwr=%d)",
+	    counter_config_val & 0x0FFF,
+	    (counter_config_val >> 12) & 0x0F,
+	    selected_tx_power);
+
+	if (reqtracker->segment_data.iov_base) {
+		free(reqtracker->segment_data.iov_base);
+		reqtracker->segment_data.iov_base = NULL;
+		reqtracker->segment_data.iov_len = 0;
+	}
+
+	if (iov->iov_len > 0) {
+		if (!util_iov_append(&reqtracker->segment_data,
+					iov->iov_base, iov->iov_len)) {
+			DBG(rap, "Failed to initialize segment accumulator");
+			return false;
+		}
+		DBG(rap, "First segment: initialized accumulator "
+		    "with %zu bytes", iov->iov_len);
+	}
+
+	if (!last_segment)
+		return false;
+
+	DBG(rap, "Single-segment: first=1, last=1");
+	return true;
+}
+
+static void ras_realtime_notify_cb(struct bt_rap *rap, uint16_t value_handle,
+				   const uint8_t *value, uint16_t length,
+				   void *user_data)
+{
+	struct iovec iov = { .iov_base = (void *)value, .iov_len = length };
+	struct segmentation_header seg_hdr;
+	struct cstracker *reqtracker;
+
+	if (!rap || !value || length == 0) {
+		DBG(rap, "Invalid notification data");
+		return;
+	}
+
+	DBG(rap, "Received real-time notification: handle=0x%04x len=%u",
+	    value_handle, length);
+
+	if (!parse_segmentation_header(&iov, &seg_hdr)) {
+		DBG(rap, "Failed to parse segmentation header");
+		return;
+	}
+
+	DBG(rap, "Segment: first=%u last=%u counter=%u",
+	    seg_hdr.first_segment, seg_hdr.last_segment,
+	    seg_hdr.rolling_segment_counter);
+
+	if (!rap->reqtracker) {
+		DBG(rap, "reqtracker is not initialised");
+		return;
+	}
+
+	reqtracker = rap->reqtracker;
+
+	if (seg_hdr.first_segment) {
+		if (!process_first_segment(rap, reqtracker, &iov,
+						seg_hdr.last_segment))
+			return;
+	} else {
+		if (iov.iov_len > 0) {
+			if (!util_iov_append(&reqtracker->segment_data,
+						iov.iov_base, iov.iov_len)) {
+				DBG(rap, "Failed to append segment data");
+				return;
+			}
+			DBG(rap, "Continuation segment: appended "
+			    "%zu bytes (total=%zu)",
+			    iov.iov_len,
+			    reqtracker->segment_data.iov_len);
+		}
+	}
+
+	/* Last segment: parse complete RAS data */
+	if (seg_hdr.last_segment)
+		parse_ras_data_segments(rap, reqtracker);
+}
+
+static void read_ras_features(struct bt_rap *rap, bool success,
+			uint8_t att_ecode,
+			const uint8_t *value, uint16_t length,
+			void *user_data)
+{
+	struct iovec iov = { .iov_base = (void *)value, .iov_len = length };
+	uint32_t features = 0;
+	struct ras *ras;
+	bool supports_realtime;
+	bool retrieve_lost;
+	bool abort_operation;
+
+	if (!success) {
+		DBG(rap, "Unable to read RAS Features: error 0x%02x",
+			att_ecode);
+		return;
+	}
+
+	ras = rap_get_ras(rap);
+
+	if (length == 0 || length > 4) {
+		DBG(rap, "Invalid RAS Features length: %u (expected 1-4)",
+			length);
+		return;
+	}
+
+	if (length < 4) {
+		uint8_t padded[4] = { 0, 0, 0, 0 };
+
+		memcpy(padded, value, length);
+		iov.iov_base = padded;
+		iov.iov_len = 4;
+		DBG(rap, "RAS Features: short read (%u bytes), zero-pad to 4",
+		    length);
+	}
+
+	if (!util_iov_pull_le32(&iov, &features)) {
+		DBG(rap, "Unable to parse RAS Features value");
+		return;
+	}
+
+	DBG(rap, "RAS Features: 0x%08x", features);
+
+	supports_realtime = (features & 0x01) != 0;
+	retrieve_lost = (features & 0x02) != 0;
+	abort_operation = (features & 0x04) != 0;
+
+	DBG(rap, "RAS Features - Real-time: %s, Retrieve Lost: %s, Abort: %s",
+	    supports_realtime ? "Yes" : "No",
+	    retrieve_lost ? "Yes" : "No",
+	    abort_operation ? "Yes" : "No");
+
+	DBG(rap, "RAS features read successfully, capabilities determined");
+
+	/* Register for real-time characteristic notifications if supported */
+	if (supports_realtime && ras && ras->realtime_chrc && rap->client) {
+		uint16_t value_handle;
+		bt_uuid_t uuid;
+
+		if (gatt_db_attribute_get_char_data(ras->realtime_chrc,
+					NULL, &value_handle,
+					NULL, NULL, &uuid)) {
+			unsigned int notify_id;
+
+			notify_id = bt_rap_register_notify(rap,
+						value_handle,
+						ras_realtime_notify_cb,
+						NULL);
+			if (!notify_id)
+				DBG(rap, "Failed to register for "
+					"real-time notifications");
+			else
+				DBG(rap, "Registered for real-time "
+					"features: id=%u", notify_id);
+		}
+	} else {
+		DBG(rap, "On demand ranging "
+			"remote device - skipping notification "
+			"registration");
+	}
+}
+
 static void foreach_rap_char(struct gatt_db_attribute *attr, void *user_data)
 {
 	struct bt_rap *rap = user_data;
@@ -1848,6 +2807,10 @@ static void foreach_rap_char(struct gatt_db_attribute *attr, void *user_data)
 			return;
 
 		ras->feat_chrc = attr;
+		if (rap->client) {
+			rap_read_value(rap, value_handle,
+					read_ras_features, rap);
+		}
 	}
 
 	if (!bt_uuid_cmp(&uuid, &uuid_realtime)) {
-- 
2.34.1


^ permalink raw reply related

* [PATCH 7.0 0215/1146] Bluetooth: SCO: check for codecs->num_codecs == 1 before assigning to sco_pi(sk)->codec
From: Greg Kroah-Hartman @ 2026-05-20 16:07 UTC (permalink / raw)
  To: stable
  Cc: Greg Kroah-Hartman, patches, Michal Luczaj,
	Luiz Augusto von Dentz, Luiz Augusto von Dentz, Marcel Holtmann,
	David Wei, linux-bluetooth, linux-kernel, Stefan Metzmacher,
	Sasha Levin
In-Reply-To: <20260520162148.390695140@linuxfoundation.org>

7.0-stable review patch.  If anyone has any objections, please let me know.

------------------

From: Stefan Metzmacher <metze@samba.org>

[ Upstream commit 4e10a9ebbf081c16517cdd9366ac618bf38d7d0c ]

copy_struct_from_sockptr() fill 'buffer' in
sco_sock_setsockopt() with zeros, so there's no
real problem.

But it actually looks strange to do this,
without checking all of codecs->codecs[0]
really comes from userspace:

  sco_pi(sk)->codec = codecs->codecs[0];

As only optlen < sizeof(struct bt_codecs) is checked
and codecs->num_codecs is not checked against != 1,
but only <= 1, and the space for the additional struct bt_codec
is not checked.

Note I don't understand bluetooth and I didn't do any runtime
tests with this! I just found it when debugging a problem
in copy_struct_from_sockptr().

I just added this to check the size is as expected:

  BUILD_BUG_ON(struct_size(codecs, codecs, 0) != 1);
  BUILD_BUG_ON(struct_size(codecs, codecs, 1) != 8);

And made sure it still compiles using this:

  make CF=-D__CHECK_ENDIAN__ W=1ce C=1 net/bluetooth/sco.o

Fixes: 3e643e4efa1e ("Bluetooth: Improve setsockopt() handling of malformed user input")
Cc: Michal Luczaj <mhal@rbox.co>
Cc: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Cc: Luiz Augusto von Dentz <luiz.dentz@gmail.com>
Cc: Marcel Holtmann <marcel@holtmann.org>
Cc: David Wei <dw@davidwei.uk>
Cc: linux-bluetooth@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
 net/bluetooth/sco.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/net/bluetooth/sco.c b/net/bluetooth/sco.c
index b84587811ef4f..18826d4b9c0bf 100644
--- a/net/bluetooth/sco.c
+++ b/net/bluetooth/sco.c
@@ -1045,7 +1045,8 @@ static int sco_sock_setsockopt(struct socket *sock, int level, int optname,
 
 		codecs = (void *)buffer;
 
-		if (codecs->num_codecs > 1) {
+		if (codecs->num_codecs != 1 ||
+		    optlen < struct_size(codecs, codecs, codecs->num_codecs)) {
 			hci_dev_put(hdev);
 			err = -EINVAL;
 			break;
-- 
2.53.0




^ permalink raw reply related

* [PATCH v7 RESEND 0/1] Bluetooth: L2CAP: Fix slab-use-after-free in l2cap_sock_cleanup_listen()
From: Siwei Zhang @ 2026-05-20 16:38 UTC (permalink / raw)
  To: Marcel Holtmann, Luiz Augusto von Dentz
  Cc: linux-bluetooth, Safa Karakuş, Siwei Zhang

Hi Bluetooth maintainers,

A public patch covering the same UAF in l2cap_sock_cleanup_listen() was posted to linux-bluetooth on April 28 
by Safa Karakuş. v4 is here:

https://lore.kernel.org/linux-bluetooth/AS8P250MB079109F82C16BEDC4F9FE584EB372@AS8P250MB0791.EURP250.PROD.OUTLOOK.COM/

I thanks for Safa's report and patch. I already reported the same issue privately to the maintainers in 
April 11th. The public patch breaks the embargo and I would like to resend my patch here.

Safa's v4 closes the sk-lifetime hole (sock_hold inside bt_accept_dequeue) but does not take conn->lock around
l2cap_chan_close, so the conn->chan_l list-corruption race in my report is still open after it.

My patch closes both: it drops the parent sk_lock, acquires conn->lock → chan->lock in the established order
to serialize the chan_l mutation, and re-takes the parent sk_lock before returning.

Crash stack and C reproducers are available upon request, only for the maintainers.

Maintainers can also refer to the email thread [Bug] KASAN: slab-use-after-free Read in l2cap_security_cfm
sent to security@kernel.org on April 11th for more details.

Detailed Timeline:

April 11th: I privately reported the issue to the maintainers and security@kernel.org
April 12th: Patch v1
April 13th: Patch v2
April 13th: Patch v3
April 14th: Patch v4
April 15th: Patch v5
May 2nd: Patch v6
May 2nd: Patch v7
May 20th: Resend v7 with a cover letter

Best,
Siwei

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

 net/bluetooth/l2cap_sock.c | 57 ++++++++++++++++++++++++++++++++------
 1 file changed, 49 insertions(+), 8 deletions(-)

-- 
2.54.0


^ permalink raw reply

* [PATCH v7 RESEND 1/1] Bluetooth: L2CAP: Fix slab-use-after-free in l2cap_sock_cleanup_listen()
From: Siwei Zhang @ 2026-05-20 16:38 UTC (permalink / raw)
  To: Marcel Holtmann, Luiz Augusto von Dentz
  Cc: linux-bluetooth, Safa Karakuş, Siwei Zhang
In-Reply-To: <20260520163859.2859782-1-oss@fourdim.xyz>

l2cap_sock_cleanup_listen() calls l2cap_chan_close() without holding
conn->lock. A concurrent task iterating conn->chan_l under conn->lock
can access a channel that has been removed from the list and freed.
That can result in a slab-use-after-free.

Split cleanup into two phases. Drain the accept queue under the parent's
sk_lock onto a local list, taking a sock reference on each child so it
survives the lock drop. Then release the parent and close every drained
child under conn->lock + chan_lock, using
l2cap_chan_hold_unless_zero()/l2cap_conn_hold_unless_zero() to cope with
a teardown that has already started, and skipping any chan whose
->data has been cleared. Reacquire the parent's sk_lock at the end so
the caller's contract is preserved.

Noted that commit ab4eedb790ca
("Bluetooth: L2CAP: Fix corrupted list in hci_chan_del")
renamed chan_lock to lock in l2cap_conn.

Fixes: 3df91ea20e74 ("Bluetooth: Revert to mutexes from RCU list")
Cc: stable@kernel.org
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Siwei Zhang <oss@fourdim.xyz>
---
 net/bluetooth/l2cap_sock.c | 57 ++++++++++++++++++++++++++++++++------
 1 file changed, 49 insertions(+), 8 deletions(-)

diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
index 71e8c1b45bce..48b018991ced 100644
--- a/net/bluetooth/l2cap_sock.c
+++ b/net/bluetooth/l2cap_sock.c
@@ -1471,27 +1471,68 @@ static int l2cap_sock_release(struct socket *sock)
 static void l2cap_sock_cleanup_listen(struct sock *parent)
 {
 	struct sock *sk;
+	struct bt_sock *bs, *tmp;
+	LIST_HEAD(killable);
 
 	BT_DBG("parent %p state %s", parent,
 	       state_to_string(parent->sk_state));
 
-	/* Close not yet accepted channels */
+	/* Drain unaccepted children to a local list, pinning each so it
+	 * survives the parent lock-drop below.
+	 */
 	while ((sk = bt_accept_dequeue(parent, NULL))) {
-		struct l2cap_chan *chan = l2cap_pi(sk)->chan;
+		sock_hold(sk);
+		list_add_tail(&bt_sk(sk)->accept_q, &killable);
+	}
 
-		BT_DBG("child chan %p state %s", chan,
-		       state_to_string(chan->state));
+	/* l2cap_chan_close() must run under conn->lock, but the rx path
+	 * (l2cap_sock_new_connection_cb) takes conn->lock then lock_sock(parent),
+	 * so parent must be released before we close.  Draining the queue
+	 * first means a concurrent cleanup_listen() on the same parent finds
+	 * it empty and is a no-op.
+	 */
+	release_sock(parent);
+
+	list_for_each_entry_safe(bs, tmp, &killable, accept_q) {
+		struct l2cap_chan *chan;
+		struct l2cap_conn *conn;
+
+		sk = (struct sock *)bs;
+		list_del_init(&bs->accept_q);
+
+		chan = l2cap_chan_hold_unless_zero(l2cap_pi(sk)->chan);
+		if (!chan) {
+			sock_put(sk);
+			continue;
+		}
 
-		l2cap_chan_hold(chan);
+		l2cap_chan_lock(chan);
+		conn = l2cap_conn_hold_unless_zero(chan->conn);
+		l2cap_chan_unlock(chan);
+
+		if (conn)
+			mutex_lock(&conn->lock);
 		l2cap_chan_lock(chan);
 
-		__clear_chan_timer(chan);
-		l2cap_chan_close(chan, ECONNRESET);
-		l2cap_sock_kill(sk);
+		BT_DBG("child chan %p state %s", chan,
+		       state_to_string(chan->state));
+
+		if (chan->data) {
+			__clear_chan_timer(chan);
+			l2cap_chan_close(chan, ECONNRESET);
+			l2cap_sock_kill(sk);
+		}
 
 		l2cap_chan_unlock(chan);
+		if (conn) {
+			mutex_unlock(&conn->lock);
+			l2cap_conn_put(conn);
+		}
 		l2cap_chan_put(chan);
+		sock_put(sk);
 	}
+
+	lock_sock_nested(parent, L2CAP_NESTING_PARENT);
 }
 
 static struct l2cap_chan *l2cap_sock_new_connection_cb(struct l2cap_chan *chan)
-- 
2.54.0


^ permalink raw reply related

* [PATCH 6.18 178/957] Bluetooth: SCO: check for codecs->num_codecs == 1 before assigning to sco_pi(sk)->codec
From: Greg Kroah-Hartman @ 2026-05-20 16:11 UTC (permalink / raw)
  To: stable
  Cc: Greg Kroah-Hartman, patches, Michal Luczaj,
	Luiz Augusto von Dentz, Luiz Augusto von Dentz, Marcel Holtmann,
	David Wei, linux-bluetooth, linux-kernel, Stefan Metzmacher,
	Sasha Levin
In-Reply-To: <20260520162134.554764788@linuxfoundation.org>

6.18-stable review patch.  If anyone has any objections, please let me know.

------------------

From: Stefan Metzmacher <metze@samba.org>

[ Upstream commit 4e10a9ebbf081c16517cdd9366ac618bf38d7d0c ]

copy_struct_from_sockptr() fill 'buffer' in
sco_sock_setsockopt() with zeros, so there's no
real problem.

But it actually looks strange to do this,
without checking all of codecs->codecs[0]
really comes from userspace:

  sco_pi(sk)->codec = codecs->codecs[0];

As only optlen < sizeof(struct bt_codecs) is checked
and codecs->num_codecs is not checked against != 1,
but only <= 1, and the space for the additional struct bt_codec
is not checked.

Note I don't understand bluetooth and I didn't do any runtime
tests with this! I just found it when debugging a problem
in copy_struct_from_sockptr().

I just added this to check the size is as expected:

  BUILD_BUG_ON(struct_size(codecs, codecs, 0) != 1);
  BUILD_BUG_ON(struct_size(codecs, codecs, 1) != 8);

And made sure it still compiles using this:

  make CF=-D__CHECK_ENDIAN__ W=1ce C=1 net/bluetooth/sco.o

Fixes: 3e643e4efa1e ("Bluetooth: Improve setsockopt() handling of malformed user input")
Cc: Michal Luczaj <mhal@rbox.co>
Cc: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Cc: Luiz Augusto von Dentz <luiz.dentz@gmail.com>
Cc: Marcel Holtmann <marcel@holtmann.org>
Cc: David Wei <dw@davidwei.uk>
Cc: linux-bluetooth@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
 net/bluetooth/sco.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/net/bluetooth/sco.c b/net/bluetooth/sco.c
index 1a38577135d44..9404fdb10ea63 100644
--- a/net/bluetooth/sco.c
+++ b/net/bluetooth/sco.c
@@ -1045,7 +1045,8 @@ static int sco_sock_setsockopt(struct socket *sock, int level, int optname,
 
 		codecs = (void *)buffer;
 
-		if (codecs->num_codecs > 1) {
+		if (codecs->num_codecs != 1 ||
+		    optlen < struct_size(codecs, codecs, codecs->num_codecs)) {
 			hci_dev_put(hdev);
 			err = -EINVAL;
 			break;
-- 
2.53.0




^ permalink raw reply related

* [PATCH 6.12 110/666] Bluetooth: SCO: check for codecs->num_codecs == 1 before assigning to sco_pi(sk)->codec
From: Greg Kroah-Hartman @ 2026-05-20 16:15 UTC (permalink / raw)
  To: stable
  Cc: Greg Kroah-Hartman, patches, Michal Luczaj,
	Luiz Augusto von Dentz, Luiz Augusto von Dentz, Marcel Holtmann,
	David Wei, linux-bluetooth, linux-kernel, Stefan Metzmacher,
	Sasha Levin
In-Reply-To: <20260520162111.222830634@linuxfoundation.org>

6.12-stable review patch.  If anyone has any objections, please let me know.

------------------

From: Stefan Metzmacher <metze@samba.org>

[ Upstream commit 4e10a9ebbf081c16517cdd9366ac618bf38d7d0c ]

copy_struct_from_sockptr() fill 'buffer' in
sco_sock_setsockopt() with zeros, so there's no
real problem.

But it actually looks strange to do this,
without checking all of codecs->codecs[0]
really comes from userspace:

  sco_pi(sk)->codec = codecs->codecs[0];

As only optlen < sizeof(struct bt_codecs) is checked
and codecs->num_codecs is not checked against != 1,
but only <= 1, and the space for the additional struct bt_codec
is not checked.

Note I don't understand bluetooth and I didn't do any runtime
tests with this! I just found it when debugging a problem
in copy_struct_from_sockptr().

I just added this to check the size is as expected:

  BUILD_BUG_ON(struct_size(codecs, codecs, 0) != 1);
  BUILD_BUG_ON(struct_size(codecs, codecs, 1) != 8);

And made sure it still compiles using this:

  make CF=-D__CHECK_ENDIAN__ W=1ce C=1 net/bluetooth/sco.o

Fixes: 3e643e4efa1e ("Bluetooth: Improve setsockopt() handling of malformed user input")
Cc: Michal Luczaj <mhal@rbox.co>
Cc: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Cc: Luiz Augusto von Dentz <luiz.dentz@gmail.com>
Cc: Marcel Holtmann <marcel@holtmann.org>
Cc: David Wei <dw@davidwei.uk>
Cc: linux-bluetooth@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
 net/bluetooth/sco.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/net/bluetooth/sco.c b/net/bluetooth/sco.c
index ded0c52ccf0b9..d915db52db221 100644
--- a/net/bluetooth/sco.c
+++ b/net/bluetooth/sco.c
@@ -978,7 +978,8 @@ static int sco_sock_setsockopt(struct socket *sock, int level, int optname,
 
 		codecs = (void *)buffer;
 
-		if (codecs->num_codecs > 1) {
+		if (codecs->num_codecs != 1 ||
+		    optlen < struct_size(codecs, codecs, codecs->num_codecs)) {
 			hci_dev_put(hdev);
 			err = -EINVAL;
 			break;
-- 
2.53.0




^ permalink raw reply related

* RE: Bluetooth: L2CAP: Fix use-after-free in l2cap_sock_new_connection_cb()
From: bluez.test.bot @ 2026-05-20 18:07 UTC (permalink / raw)
  To: linux-bluetooth, oss
In-Reply-To: <20260520162030.2842543-2-oss@fourdim.xyz>

[-- Attachment #1: Type: text/plain, Size: 1045 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=1098163

---Test result---

Test Summary:
CheckPatch                    PASS      1.67 seconds
GitLint                       PASS      0.34 seconds
SubjectPrefix                 PASS      0.13 seconds
BuildKernel                   PASS      24.93 seconds
CheckAllWarning               PASS      27.79 seconds
CheckSparse                   PASS      26.40 seconds
BuildKernel32                 PASS      24.49 seconds
TestRunnerSetup               PASS      521.38 seconds
TestRunner_l2cap-tester       PASS      376.22 seconds
TestRunner_smp-tester         PASS      17.96 seconds
TestRunner_6lowpan-tester     PASS      50.91 seconds
IncrementalBuild              PASS      24.22 seconds



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

---
Regards,
Linux Bluetooth


^ permalink raw reply

* RE: Bluetooth: L2CAP: Fix slab-use-after-free in l2cap_sock_cleanup_listen()
From: bluez.test.bot @ 2026-05-20 18:08 UTC (permalink / raw)
  To: linux-bluetooth, oss
In-Reply-To: <20260520163859.2859782-2-oss@fourdim.xyz>

[-- Attachment #1: Type: text/plain, Size: 1604 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=1098174

---Test result---

Test Summary:
CheckPatch                    PASS      0.68 seconds
GitLint                       FAIL      0.27 seconds
SubjectPrefix                 PASS      0.10 seconds
BuildKernel                   PASS      25.43 seconds
CheckAllWarning               PASS      27.91 seconds
CheckSparse                   PASS      26.60 seconds
BuildKernel32                 PASS      24.81 seconds
TestRunnerSetup               PASS      533.99 seconds
TestRunner_l2cap-tester       PASS      376.93 seconds
IncrementalBuild              PASS      23.53 seconds

Details
##############################
Test: GitLint - FAIL
Desc: Run gitlint
Output:
[v7,RESEND,1/1] Bluetooth: L2CAP: Fix slab-use-after-free in l2cap_sock_cleanup_listen()

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
1: T1 Title exceeds max length (88>80): "[v7,RESEND,1/1] Bluetooth: L2CAP: Fix slab-use-after-free in l2cap_sock_cleanup_listen()"


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

---
Regards,
Linux Bluetooth


^ permalink raw reply

* Re: [PATCH BlueZ v1] shared/rap: Add client real-time ranging registration and notification parsing
From: Luiz Augusto von Dentz @ 2026-05-20 18:14 UTC (permalink / raw)
  To: Prathibha Madugonde
  Cc: linux-bluetooth, quic_mohamull, quic_hbandi, quic_anubhavg
In-Reply-To: <20260520163037.1823823-1-prathm@qti.qualcomm.com>

Hi Prathibha,

On Wed, May 20, 2026 at 12:30 PM Prathibha Madugonde
<prathibha.madugonde@oss.qualcomm.com> wrote:
>
> From: Prathibha Madugonde <prathibha.madugonde@oss.qualcomm.com>
>
> Read the RAS Features characteristic to determine whether the remote
> device supports real-time ranging. If supported, register for real-time
> characteristic notifications using the reqtracker for the CS initiator
> role.
>
> Parse incoming segmented RAS ranging data notifications by accumulating
> segments via iovec and parsing complete subevent headers and CS mode 0-3
> step data, including IQ/tone PCT samples, once the last segment arrives.
> ---
>  src/shared/rap.c | 967 ++++++++++++++++++++++++++++++++++++++++++++++-
>  1 file changed, 965 insertions(+), 2 deletions(-)
>
> diff --git a/src/shared/rap.c b/src/shared/rap.c
> index 145da2060..eabc6d543 100644
> --- a/src/shared/rap.c
> +++ b/src/shared/rap.c
> @@ -29,6 +29,9 @@
>  #define DBG(_rap, fmt, ...) \
>         rap_debug(_rap, "%s:%s() " fmt, __FILE__, __func__, ##__VA_ARGS__)
>
> +#define SIGN_EXTEND_TO_16(val, bits) \
> +       ((int16_t)(((val) ^ (1U << ((bits)-1))) - (1U << ((bits)-1))))
> +
>  #define RAS_UUID16                     0x185B
>
>  /* Total number of attribute handles reserved for the RAS service */
> @@ -43,6 +46,11 @@
>  #define RAS_STEP_ABORTED_BIT   0x80/* set step aborted */
>  #define RAS_SUBEVENT_HEADER_SIZE 8
>
> +#define CS_MODE_ZERO_WIRE_INIT_SIZE 7
> +#define CS_MODE_ZERO_WIRE_REF_SIZE 3
> +#define CS_MODE_ONE_WIRE_SIZE_MIN 6
> +#define CS_MODE_ONE_WIRE_SIZE_MAX 12
> +
>  enum pct_format {
>         IQ = 0,
>         PHASE = 1,
> @@ -134,6 +142,28 @@ static inline void ranging_header_set_pct_format(struct ranging_header *hdr,
>                                 ((format & 0x03) << 6);
>  }
>
> +static inline uint8_t ranging_header_get_antenna_mask(
> +                                       const struct ranging_header *hdr)
> +{
> +       if (!hdr)
> +               return 0;
> +
> +       return hdr->antenna_pct & 0x0F;
> +}
> +
> +static inline uint8_t antenna_mask_count_paths(uint8_t antenna_mask)
> +{
> +       uint8_t count = 0;
> +       uint8_t i;
> +
> +       for (i = 0; i < 4; i++) {
> +               if (antenna_mask & (1u << i))
> +                       count++;
> +       }
> +
> +       return count;
> +}
> +
>  struct ras_subevent_header {
>         uint16_t start_acl_conn_event;
>         uint16_t frequency_compensation;
> @@ -149,9 +179,21 @@ struct ras_subevent_header {
>  #define RAS_DONE_STATUS_PACK(ranging, subevent) \
>         ((uint8_t)(((ranging) & 0x0F) | (((subevent) & 0x0F) << 4)))
>
> +#define RAS_DONE_STATUS_UNPACK_RANGING(packed) \
> +       ((uint8_t)((packed) & 0x0F))
> +
> +#define RAS_DONE_STATUS_UNPACK_SUBEVENT(packed) \
> +       ((uint8_t)(((packed) >> 4) & 0x0F))
> +
>  #define RAS_ABORT_REASON_PACK(ranging, subevent) \
>         ((uint8_t)(((ranging) & 0x0F) | (((subevent) & 0x0F) << 4)))
>
> +#define RAS_ABORT_REASON_UNPACK_RANGING(packed) \
> +       ((uint8_t)((packed) & 0x0F))
> +
> +#define RAS_ABORT_REASON_UNPACK_SUBEVENT(packed) \
> +       ((uint8_t)(((packed) >> 4) & 0x0F))
> +
>  struct ras_subevent {
>         struct ras_subevent_header subevent_header;
>         uint8_t subevent_data[];
> @@ -206,6 +248,11 @@ struct cstracker {
>         uint16_t last_start_acl_conn_evt_counter;
>         uint16_t last_freq_comp;
>         int8_t last_ref_pwr_lvl;
> +
> +       /* Client - first segment carries this */
> +       struct ranging_header   ranging_header_;
> +       /* Client - subsequent segments appended using iovec */
> +       struct iovec segment_data;
>  };
>
>  /* Ranging Service context */
> @@ -254,6 +301,7 @@ struct bt_rap {
>         void *debug_data;
>         void *user_data;
>         struct cstracker *resptracker;
> +       struct cstracker *reqtracker;
>  };
>
>  static struct queue *rap_db;
> @@ -274,6 +322,28 @@ struct bt_rap_ready {
>         void *data;
>  };
>
> +typedef void (*rap_notify_t)(struct bt_rap *rap, uint16_t value_handle,
> +                       const uint8_t *value, uint16_t length,
> +                       void *user_data);
> +
> +struct bt_rap_notify {
> +       unsigned int id;
> +       struct bt_rap *rap;
> +       rap_notify_t func;
> +       void *user_data;
> +};
> +
> +typedef void (*rap_func_t)(struct bt_rap *rap, bool success,
> +                       uint8_t att_ecode, const uint8_t *value,
> +                       uint16_t length, void *user_data);
> +
> +struct bt_rap_pending {
> +       unsigned int id;
> +       struct bt_rap *rap;
> +       rap_func_t func;
> +       void *userdata;
> +};
> +
>  uint16_t default_ras_mtu = 247; /*Section 3.1.2 of RAP 1.0*/
>  uint8_t ras_segment_header_size = 1;
>
> @@ -542,6 +612,11 @@ static void rap_free(void *data)
>                 rap->resptracker = NULL;
>         }
>
> +       if (rap->reqtracker) {
> +               free(rap->reqtracker);
> +               rap->reqtracker = NULL;
> +       }
> +
>         queue_destroy(rap->notify, free);
>         queue_destroy(rap->pending, NULL);
>         queue_destroy(rap->ready_cbs, rap_ready_free);
> @@ -641,6 +716,12 @@ static void cs_tracker_init(struct cstracker *t)
>         t->last_start_acl_conn_evt_counter = 0;
>         t->last_freq_comp = 0;
>         t->last_ref_pwr_lvl = 0;
> +
> +       /* Initialize ranging header using helper functions */
> +       memset(&t->ranging_header_, 0, sizeof(t->ranging_header_));
> +       /* Initialize segment accumulator */
> +       t->segment_data.iov_base = NULL;
> +       t->segment_data.iov_len = 0;
>  }
>
>  static void ras_features_read_cb(struct gatt_db_attribute *attrib,
> @@ -1698,6 +1779,46 @@ static void form_ras_data_with_cs_subevent_result_cont(struct bt_rap *rap,
>                 cont->step_data);
>  }
>
> +static void fill_initiator_data_from_cs_subevent_result_cont(struct bt_rap *rap,
> +               const struct rap_ev_cs_subevent_result_cont *cont,
> +               uint16_t length)
> +{
> +       size_t base_len = offsetof(struct rap_ev_cs_subevent_result_cont,
> +                                       step_data);
> +       struct cstracker *reqtracker;
> +
> +       if (!rap || !rap->reqtracker || !cont)
> +               return;
> +
> +       if (length < base_len)
> +               return;
> +
> +       DBG(rap, "Received CS subevent result continue subevent: len=%d",
> +               length);
> +
> +       reqtracker = rap->reqtracker;
> +
> +}
> +
> +static void fill_initiator_data_from_cs_subevent_result(struct bt_rap *rap,
> +               const struct rap_ev_cs_subevent_result *data,
> +               uint16_t length)
> +{
> +       size_t base_len = offsetof(struct rap_ev_cs_subevent_result,
> +                                       step_data);
> +
> +       if (!rap || !rap->reqtracker || !data)
> +               return;
> +
> +       /* Defensive check: base header must be present */
> +       if (length < base_len)
> +               return;
> +
> +       DBG(rap, "Received CS subevent result subevent: len=%d", length);
> +
> +
> +}
> +
>  void bt_rap_hci_cs_subevent_result_cont_callback(uint16_t length,
>                                                 const void *param,
>                                                 void *user_data)
> @@ -1707,7 +1828,12 @@ void bt_rap_hci_cs_subevent_result_cont_callback(uint16_t length,
>
>         DBG(rap, "Received CS subevent CONT: len=%d", length);
>
> -       form_ras_data_with_cs_subevent_result_cont(rap, cont, length);
> +       if (rap->resptracker->role == CS_REFLECTOR)
> +               form_ras_data_with_cs_subevent_result_cont(rap, cont, length);
> +
> +       if (rap->reqtracker->role == CS_INITIATOR)
> +               fill_initiator_data_from_cs_subevent_result_cont(rap, cont,
> +                                                               length);
>  }
>
>  void bt_rap_hci_cs_subevent_result_callback(uint16_t length,
> @@ -1720,7 +1846,11 @@ void bt_rap_hci_cs_subevent_result_callback(uint16_t length,
>         DBG(rap, "Received CS subevent: len=%d", length);
>
>         /* Populate CsProcedureData and send RAS payload */
> -       form_ras_data_with_cs_subevent_result(rap, data, length);
> +       if (rap->resptracker->role == CS_REFLECTOR)
> +               form_ras_data_with_cs_subevent_result(rap, data, length);
> +
> +       if (rap->reqtracker->role == CS_INITIATOR)
> +               fill_initiator_data_from_cs_subevent_result(rap, data, length);
>  }
>
>  void bt_rap_hci_cs_procedure_enable_complete_callback(uint16_t length,
> @@ -1763,6 +1893,7 @@ void bt_rap_hci_cs_config_complete_callback(uint16_t length,
>         const struct rap_ev_cs_config_cmplt *data = param;
>         struct bt_rap *rap = user_data;
>         struct cstracker *resptracker;
> +       struct cstracker *reqtracker;
>
>         if (!rap)
>                 return;
> @@ -1781,6 +1912,19 @@ void bt_rap_hci_cs_config_complete_callback(uint16_t length,
>         resptracker->config_id = data->config_id;
>         resptracker->role = data->role;
>         resptracker->rtt_type = data->rtt_type;
> +
> +       if (!rap->reqtracker) {
> +               reqtracker = new0(struct cstracker, 1);
> +               cs_tracker_init(reqtracker);
> +               rap->reqtracker = reqtracker;
> +       }
> +
> +       reqtracker = rap->reqtracker;
> +
> +       /* Basic fields */
> +       reqtracker->config_id = data->config_id;
> +       reqtracker->role = data->role;
> +       reqtracker->rtt_type = data->rtt_type;
>  }
>
>  struct bt_rap *bt_rap_new(struct gatt_db *ldb, struct gatt_db *rdb)
> @@ -1815,6 +1959,821 @@ done:
>         return rap;
>  }
>
> +static void ras_pending_destroy(void *data)
> +{
> +       struct bt_rap_pending *pending = data;
> +       struct bt_rap *rap = pending->rap;
> +
> +       if (queue_remove_if(rap->pending, NULL, pending))
> +               free(pending);
> +}
> +
> +static void ras_pending_complete(bool success, uint8_t att_ecode,
> +                               const uint8_t *value, uint16_t length,
> +                               void *user_data)
> +{
> +       struct bt_rap_pending *pending = user_data;
> +
> +       if (pending->func)
> +               pending->func(pending->rap, success, att_ecode, value, length,
> +                                         pending->userdata);
> +}
> +
> +static void rap_read_value(struct bt_rap *rap, uint16_t value_handle,
> +                          rap_func_t func, void *user_data)
> +{
> +       struct bt_rap_pending *pending;
> +
> +       pending = new0(struct bt_rap_pending, 1);
> +       pending->rap = rap;
> +       pending->func = func;
> +       pending->userdata = user_data;
> +
> +       pending->id = bt_gatt_client_read_value(rap->client, value_handle,
> +                                               ras_pending_complete, pending,
> +                                               ras_pending_destroy);
> +       if (!pending->id) {
> +               DBG(rap, "Unable to send Read request");
> +               free(pending);
> +               return;
> +       }
> +
> +       queue_push_tail(rap->pending, pending);
> +}
> +
> +static void ras_register(uint16_t att_ecode, void *user_data)
> +{
> +       struct bt_rap_notify *notify = user_data;
> +
> +       if (att_ecode)
> +               DBG(notify->rap, "RAS register failed 0x%04x", att_ecode);
> +
> +       (void)notify;
> +}
> +
> +static void rap_notify(uint16_t value_handle, const uint8_t *value,
> +                               uint16_t length, void *user_data)
> +{
> +       struct bt_rap_notify *notify = user_data;
> +
> +       if (notify->func)
> +               notify->func(notify->rap, value_handle, value, length,
> +                                        notify->user_data);
> +}
> +
> +static void rap_notify_destroy(void *data)
> +{
> +       struct bt_rap_notify *notify = data;
> +       struct bt_rap *rap = notify->rap;
> +
> +       if (queue_remove_if(rap->notify, NULL, notify))
> +               free(notify);
> +}
> +
> +static unsigned int bt_rap_register_notify(struct bt_rap *rap,
> +                                       uint16_t value_handle,
> +                                       rap_notify_t func,
> +                                       void *user_data)
> +{
> +       struct bt_rap_notify *notify;
> +
> +       notify = new0(struct bt_rap_notify, 1);
> +       notify->rap = rap;
> +       notify->func = func;
> +       notify->user_data = user_data;
> +
> +       DBG(rap, "register for notifications");
> +
> +       notify->id = bt_gatt_client_register_notify(rap->client,
> +                                       value_handle, ras_register,
> +                                       rap_notify, notify,
> +                                       rap_notify_destroy);
> +       if (!notify->id) {
> +               DBG(rap, "Unable to register for notifications");
> +               free(notify);
> +               return 0;
> +       }
> +
> +       queue_push_tail(rap->notify, notify);
> +
> +       return notify->id;
> +}
> +
> +static inline bool parse_segmentation_header(struct iovec *iov,
> +                                            struct segmentation_header *s)
> +{
> +       uint8_t byte;
> +
> +       if (!util_iov_pull_u8(iov, &byte))
> +               return false;
> +
> +       s->first_segment = (byte & 0x01) ? 1 : 0;
> +       s->last_segment = (byte & 0x02) ? 1 : 0;
> +       s->rolling_segment_counter = (byte >> 2) & 0x3F;
> +
> +       return true;
> +}
> +
> +static void parse_i_q_sample(struct iovec *iov, int16_t *i_sample,
> +                               int16_t *q_sample)
> +{
> +       uint32_t buffer;
> +       uint32_t i12;
> +       uint32_t q12;
> +
> +       if (!util_iov_pull_le24(iov, &buffer)) {
> +               *i_sample = 0;
> +               *q_sample = 0;
> +               return;
> +       }
> +
> +       i12 =  buffer        & 0x0FFFU;   /* bits 0..11 */
> +       q12 = (buffer >> 12) & 0x0FFFU;   /* bits 12..23 */
> +
> +       *i_sample = SIGN_EXTEND_TO_16(i12, 12);
> +       *q_sample = SIGN_EXTEND_TO_16(q12, 12);
> +}
> +
> +static size_t get_mode_zero_length(enum cs_role remote_role)
> +{
> +       return (remote_role == CS_ROLE_INITIATOR) ?
> +               CS_MODE_ZERO_WIRE_INIT_SIZE :
> +               CS_MODE_ZERO_WIRE_REF_SIZE;
> +}
> +
> +static void parse_mode_zero(struct bt_rap *rap, struct iovec *mode_iov,
> +                           enum cs_role remote_role)
> +{
> +       uint8_t packet_quality;
> +       int8_t packet_rssi_dbm;
> +       uint8_t packet_ant;
> +       uint32_t init_measured_freq_offset = 0;
> +
> +       if (!util_iov_pull_u8(mode_iov, &packet_quality) ||
> +           !util_iov_pull_u8(mode_iov, (uint8_t *)&packet_rssi_dbm) ||
> +           !util_iov_pull_u8(mode_iov, &packet_ant)) {
> +               DBG(rap, "Mode 0: failed to parse common fields");
> +               return;
> +       }
> +
> +       if (remote_role == CS_ROLE_INITIATOR) {
> +               if (!util_iov_pull_le32(mode_iov, &init_measured_freq_offset)) {
> +                       DBG(rap, "Mode 0: failed to parse freq offset");
> +                       return;
> +               }
> +       }
> +
> +       if (remote_role == CS_ROLE_INITIATOR) {
> +               DBG(rap, "    cs_mode_zero_data (INITIATOR): "
> +                       "Packet_Quality=%u Packet_RSSI=%d "
> +                       "Packet_Antenna=%u Measured_Freq_Offset=%u",
> +                       packet_quality,
> +                       packet_rssi_dbm,
> +                       packet_ant,
> +                       init_measured_freq_offset);
> +       } else {
> +               DBG(rap, "    cs_mode_zero_data (REFLECTOR): "
> +                       "Packet_Quality=%u Packet_RSSI=%d "
> +                       "Packet_Antenna=%u",
> +                       packet_quality,
> +                       packet_rssi_dbm,
> +                       packet_ant);
> +       }
> +}
> +
> +static size_t get_mode_one_length(bool include_pct)
> +{
> +       if (include_pct)
> +               return CS_MODE_ONE_WIRE_SIZE_MAX;
> +       return CS_MODE_ONE_WIRE_SIZE_MIN;
> +}
> +
> +static void parse_mode_one(struct bt_rap *rap, struct iovec *mode_iov,
> +                          enum cs_role remote_role, bool include_pct)
> +{
> +       uint8_t packet_quality;
> +       uint8_t packet_nadm;
> +       int8_t packet_rssi_dbm;
> +       int16_t time_value;
> +       uint8_t packet_ant;
> +       int16_t pct1_i = 0, pct1_q = 0;
> +       int16_t pct2_i = 0, pct2_q = 0;
> +
> +       /* Parse fixed fields (6 bytes) using util_iov_pull_* */
> +       if (!util_iov_pull_u8(mode_iov, &packet_quality) ||
> +           !util_iov_pull_u8(mode_iov, &packet_nadm) ||
> +           !util_iov_pull_u8(mode_iov, (uint8_t *)&packet_rssi_dbm) ||
> +           !util_iov_pull_le16(mode_iov, (uint16_t *)&time_value) ||
> +           !util_iov_pull_u8(mode_iov, &packet_ant)) {
> +               DBG(rap, "Mode 1: failed to parse fixed fields");
> +               return;
> +       }
> +
> +       /* Parse optional PCT fields using parse_i_q_sample */
> +       if (include_pct) {
> +               parse_i_q_sample(mode_iov, &pct1_i, &pct1_q);
> +               parse_i_q_sample(mode_iov, &pct2_i, &pct2_q);
> +       }
> +
> +       if (remote_role == CS_ROLE_INITIATOR) {
> +               DBG(rap, "    cs_mode_one_data (INITIATOR): "
> +                       "Packet_Quality=%u Packet_NADM=%u "
> +                       "Packet_RSSI=%d ToA_ToD_Initiator=0x%4.4x "
> +                       "Packet_Antenna=%u",
> +                       packet_quality,
> +                       packet_nadm,
> +                       packet_rssi_dbm,
> +                       time_value,
> +                       packet_ant);
> +               if (include_pct) {
> +                       DBG(rap, "    cs_mode_one_data (INITIATOR): "
> +                               "Packet_PCT1(I=0x%3.3x,Q=0x%3.3x) "
> +                               "Packet_PCT2(I=0x%3.3x,Q=0x%3.3x)",
> +                               pct1_i, pct1_q,
> +                               pct2_i, pct2_q);
> +               }
> +       } else {
> +               DBG(rap, "    cs_mode_one_data (REFLECTOR): "
> +                       "Packet_Quality=%u Packet_NADM=%u "
> +                       "Packet_RSSI=%d ToD_ToA_Reflector=0x%4.4x  "
> +                       "Packet_Antenna=%u",
> +                       packet_quality,
> +                       packet_nadm,
> +                       packet_rssi_dbm,
> +                       time_value,
> +                       packet_ant);
> +               if (include_pct) {
> +                       DBG(rap, "    cs_mode_one_data (REFLECTOR): "
> +                               "Packet_PCT1(I=0x%3.3x,Q=0x%3.3x) "
> +                               "Packet_PCT2(I=0x%3.3x,Q=0x%3.3x)",
> +                               pct1_i, pct1_q,
> +                               pct2_i, pct2_q);
> +               }
> +       }
> +}
> +
> +static size_t get_mode_two_length(uint8_t num_antenna_paths)
> +{
> +       uint8_t num_tone_data = num_antenna_paths + 1;
> +
> +       return 1 + (4 * num_tone_data);
> +}
> +
> +static void parse_mode_two(struct bt_rap *rap, struct iovec *mode_iov)
> +{
> +       uint8_t ant_perm_index;
> +       int16_t tone_pct_i[5];
> +       int16_t tone_pct_q[5];
> +       uint8_t tone_quality[5];
> +       uint8_t k;
> +       uint8_t max_paths = 5;
> +
> +       if (!util_iov_pull_u8(mode_iov, &ant_perm_index)) {
> +               DBG(rap, "Mode 2: failed to parse ant_perm_index");
> +               return;
> +       }
> +
> +       for (k = 0; k < max_paths; k++) {
> +               int16_t i_val, q_val;
> +
> +               if (mode_iov->iov_len < 4) {
> +                       DBG(rap, "Mode 2: insufficient PCT for "
> +                               "path %u (rem=%zu)",
> +                               k, mode_iov->iov_len);
> +                       break;
> +               }
> +               parse_i_q_sample(mode_iov, &i_val, &q_val);
> +               tone_pct_i[k] = i_val;
> +               tone_pct_q[k] = q_val;
> +
> +               util_iov_pull_u8(mode_iov, &tone_quality[k]);
> +               DBG(rap, "tone_quality_indicator : %d",
> +                       tone_quality[k]);
> +               DBG(rap, "[i, q] : %d, %d",
> +                       tone_pct_i[k],
> +                       tone_pct_q[k]);
> +       }
> +
> +       DBG(rap, "    cs_mode_two_data: ant_perm_idx=%u",
> +               ant_perm_index);
> +       for (k = 0; k < max_paths; k++) {
> +               DBG(rap, "      tone[%u]: pct(I=0x%3.3x,Q=0x%3.3x) quality=%u",
> +                       k,
> +                       tone_pct_i[k],
> +                       tone_pct_q[k],
> +                       tone_quality[k]);
> +       }
> +}
> +
> +static size_t get_mode_three_length(uint8_t num_antenna_paths, bool include_pct)
> +{
> +       uint8_t num_tone_data = num_antenna_paths + 1;
> +       size_t len;
> +
> +       /* Mode 1 portion: 6 bytes base + optional 6 bytes PCT */
> +       len = 6;
> +       if (include_pct)
> +               len += 6;
> +
> +       /* Mode 2 portion: 1 byte antenna index + 4 bytes per tone */
> +       len += 1 + (4 * num_tone_data);
> +
> +       return len;
> +}
> +
> +static void parse_mode_three(struct bt_rap *rap, struct iovec *mode_iov,
> +                            enum cs_role remote_role, bool include_pct)
> +{
> +       uint8_t packet_quality;
> +       uint8_t packet_nadm;
> +       int8_t packet_rssi_dbm;
> +       int16_t time_value;
> +       uint8_t packet_ant;
> +       int16_t pct1_i = 0, pct1_q = 0;
> +       int16_t pct2_i = 0, pct2_q = 0;
> +       uint8_t ant_perm_index;
> +       int16_t tone_pct_i[5];
> +       int16_t tone_pct_q[5];
> +       uint8_t tone_quality[5];
> +       uint8_t k;
> +       uint8_t max_paths = 5;
> +
> +       /* Parse Mode 1 portion (6 or 12 bytes) */
> +       if (!util_iov_pull_u8(mode_iov, &packet_quality) ||
> +           !util_iov_pull_u8(mode_iov, &packet_nadm) ||
> +           !util_iov_pull_u8(mode_iov, (uint8_t *)&packet_rssi_dbm) ||
> +           !util_iov_pull_le16(mode_iov, (uint16_t *)&time_value) ||
> +           !util_iov_pull_u8(mode_iov, &packet_ant)) {
> +               DBG(rap, "Mode 3: failed to parse Mode 1 fixed fields");
> +               return;
> +       }
> +
> +       /* Parse optional PCT fields using new logic */
> +       if (include_pct) {
> +               parse_i_q_sample(mode_iov, &pct1_i, &pct1_q);
> +               parse_i_q_sample(mode_iov, &pct2_i, &pct2_q);
> +       }
> +
> +       /* Parse Mode 2 portion - antenna permutation index */
> +       if (!util_iov_pull_u8(mode_iov, &ant_perm_index)) {
> +               DBG(rap, "Mode 3: failed to parse ant_perm_index");
> +               return;
> +       }
> +
> +       /* Parse tone paths using new logic */
> +       for (k = 0; k < max_paths; k++) {
> +               int16_t i_val, q_val;
> +
> +               if (mode_iov->iov_len < 4) {
> +                       DBG(rap, "Mode 3: insufficient PCT for "
> +                               "path %u (rem=%zu)",
> +                               k, mode_iov->iov_len);
> +                       break;
> +               }

There should be empty lines after an if block.

> +               parse_i_q_sample(mode_iov, &i_val, &q_val);
> +               tone_pct_i[k] = i_val;
> +               tone_pct_q[k] = q_val;
> +
> +               util_iov_pull_u8(mode_iov, &tone_quality[k]);
> +               DBG(rap, "tone_quality_indicator : %d",
> +                       tone_quality[k]);
> +               DBG(rap, "[i, q] : %d, %d",
> +                       tone_pct_i[k],
> +                       tone_pct_q[k]);
> +       }
> +
> +       if (remote_role == CS_ROLE_INITIATOR) {
> +               DBG(rap, "    cs_mode_three_data (INITIATOR): "
> +                       "Packet_Quality=%u Packet_NADM=%u "
> +                       "Packet_RSSI=%d ToA_ToD_Initiator=0x%4.4x "
> +                       "Packet_Antenna=%u",
> +                       packet_quality,
> +                       packet_nadm,
> +                       packet_rssi_dbm,
> +                       time_value,
> +                       packet_ant);
> +               if (include_pct) {
> +                       DBG(rap, "    cs_mode_three_data (INITIATOR): "
> +                               "Packet_PCT1(I=0x%3.3x,Q=0x%3.3x) "
> +                               "Packet_PCT2(I=0x%3.3x,Q=0x%3.3x)",
> +                               pct1_i, pct1_q,
> +                               pct2_i, pct2_q);
> +               }
> +               DBG(rap, "    cs_mode_three_data (INITIATOR): "
> +                       "Antenna_Permutation_Index=%u",
> +                       ant_perm_index);
> +               for (k = 0; k < max_paths; k++) {
> +                       DBG(rap, "      Tone_PCT[%u](I=0x%3.3x,Q=0x%3.3x) "
> +                               "Tone_Quality_Indicator[%u]=%u",
> +                               k,
> +                               tone_pct_i[k],
> +                               tone_pct_q[k],
> +                               k,
> +                               tone_quality[k]);

You are adding a lot of debugging, perhaps these should be disabled if
debug is disabled so we don't iterate if nothing will be printed.

> +               }
> +       } else {
> +               DBG(rap, "    cs_mode_three_data (REFLECTOR): "
> +                       "Packet_Quality=%u Packet_NADM=%u "
> +                       "Packet_RSSI=%d ToD_ToA_Reflector=0x%4.4x "
> +                       "Packet_Antenna=%u",
> +                       packet_quality,
> +                       packet_nadm,
> +                       packet_rssi_dbm,
> +                       time_value,
> +                       packet_ant);
> +               if (include_pct) {
> +                       DBG(rap, "    cs_mode_three_data (REFLECTOR): "
> +                               "Packet_PCT1(I=0x%3.3x,Q=0x%3.3x) "
> +                               "Packet_PCT2(I=0x%3.3x,Q=0x%3.3x)",
> +                               pct1_i, pct1_q,
> +                               pct2_i, pct2_q);
> +               }
> +               DBG(rap, "    cs_mode_three_data (REFLECTOR): "
> +                       "Antenna_Permutation_Index=%u",
> +                       ant_perm_index);
> +               for (k = 0; k < max_paths; k++) {
> +                       DBG(rap, "      Tone_PCT[%u](I=0x%3.3x,Q=0x%3.3x) "
> +                               "Tone_Quality_Indicator[%u]=%u",
> +                               k,
> +                               tone_pct_i[k],
> +                               tone_pct_q[k],
> +                               k,
> +                               tone_quality[k]);

Ditto, I just skip early if nothing will be done with this data.

> +               }
> +       }
> +}
> +
> +static void parse_ras_data_segments(struct bt_rap *rap,
> +                              struct cstracker *reqtracker)
> +{
> +       const uint8_t *buf;
> +       size_t buf_len;
> +       size_t offset = 0;
> +       uint8_t antenna_mask;
> +       uint8_t num_antenna_paths;
> +
> +       if (!rap || !reqtracker)
> +               return;
> +
> +       DBG(rap, "Complete RAS data received: %zu bytes",
> +           reqtracker->segment_data.iov_len);
> +
> +       /* Extract num_antenna_paths from ranging header */
> +       antenna_mask =
> +               ranging_header_get_antenna_mask(&reqtracker->ranging_header_);
> +       num_antenna_paths = antenna_mask_count_paths(antenna_mask);
> +
> +       /* Validate complete RAS data structure before parsing */
> +       buf = (const uint8_t *)reqtracker->segment_data.iov_base;
> +       buf_len = reqtracker->segment_data.iov_len;

Ok, this is part sounds a little suspisus, using a pointer to validate
dat when it was already part of an iovec sound backwards to me, Id
trust a lot more if that would be done via iovec, you can just load
another local variable if you want to perform an extra step of
validating it, but if it just bounds checking then it should be
possible to perform it while parsing.

> +       /* Process all subevents in the accumulated buffer */
> +       while (offset + RAS_SUBEVENT_HEADER_SIZE <= buf_len) {
> +               struct ras_subevent_header subevt_hdr;
> +               const uint8_t *hdr = buf + offset;
> +               uint8_t done_byte;
> +               uint8_t abort_byte;
> +
> +               subevt_hdr.start_acl_conn_event = get_le16(hdr);
> +               subevt_hdr.frequency_compensation =
> +                       get_le16(hdr + 2);
> +
> +               done_byte = hdr[4];
> +               subevt_hdr.ranging_done_status =
> +                       RAS_DONE_STATUS_UNPACK_RANGING(done_byte);
> +               subevt_hdr.subevent_done_status =
> +                       RAS_DONE_STATUS_UNPACK_SUBEVENT(done_byte);
> +
> +               abort_byte = hdr[5];
> +               subevt_hdr.ranging_abort_reason =
> +                       RAS_ABORT_REASON_UNPACK_RANGING(abort_byte);
> +               subevt_hdr.subevent_abort_reason =
> +                       RAS_ABORT_REASON_UNPACK_SUBEVENT(abort_byte);
> +
> +               subevt_hdr.reference_power_level = (int8_t)hdr[6];
> +               subevt_hdr.num_steps_reported = hdr[7];
> +
> +               DBG(rap, "Parsed subevent: start_acl=%u "
> +                       "freq_comp=%d ref_pwr=%d steps=%u",
> +                   subevt_hdr.start_acl_conn_event,
> +                   subevt_hdr.frequency_compensation,
> +                   subevt_hdr.reference_power_level,
> +                   subevt_hdr.num_steps_reported);
> +
> +               offset += RAS_SUBEVENT_HEADER_SIZE;
> +
> +               /* Parse step data for this subevent */
> +               for (uint8_t step_idx = 0;
> +                    step_idx < subevt_hdr.num_steps_reported;
> +                    step_idx++) {

Loops inside another another is already a sign this should be split
into helper functions so we avoid too many level of identation which
make it hard to read and very easy to hit 80 columns.

> +                       uint8_t mode_byte;
> +                       bool step_aborted;
> +                       uint8_t step_mode;
> +                       size_t step_payload_len = 0;
> +                       uint8_t rtt_type;
> +                       bool include_pct;
> +                       /* Determine remote role */
> +                       enum cs_role local_role = reqtracker->role;
> +                       enum cs_role remote_role =
> +                               (local_role == CS_ROLE_INITIATOR) ?
> +                               CS_ROLE_REFLECTOR : CS_ROLE_INITIATOR;
> +
> +                       if (offset >= buf_len) {
> +                               DBG(rap, "Insufficient data for "
> +                                       "step %u", step_idx);
> +                               break;
> +                       }
> +
> +                       /* Parse: 1 byte mode + variable payload */
> +                       mode_byte = buf[offset++];
> +                       step_aborted = (mode_byte & RAS_STEP_ABORTED_BIT) != 0;
> +                       step_mode = mode_byte & 0x7F;
> +
> +                       if (step_aborted) {
> +                               DBG(rap, "  Step %u: mode=%u "
> +                                       "(aborted)",
> +                                       step_idx, step_mode);
> +                               continue;
> +                       }
> +
> +                       /* Determine payload length and parse mode data */
> +                       rtt_type = reqtracker->rtt_type;
> +                       include_pct = (rtt_type == 0x01 || rtt_type == 0x02);
> +
> +                       switch (step_mode) {
> +                       case CS_MODE_ZERO:
> +                               step_payload_len =
> +                                       get_mode_zero_length(remote_role);
> +                               break;
> +                       case CS_MODE_ONE:
> +                               step_payload_len =
> +                                       get_mode_one_length(include_pct);
> +                               break;
> +                       case CS_MODE_TWO:
> +                               step_payload_len =
> +                                       get_mode_two_length(num_antenna_paths);
> +                               break;
> +                       case CS_MODE_THREE:
> +                               step_payload_len =
> +                                       get_mode_three_length(
> +                                               num_antenna_paths,
> +                                               include_pct);
> +                               break;
> +                       default:
> +                               DBG(rap, "  Step %u: unknown mode=%u",
> +                                       step_idx, step_mode);
> +                               step_payload_len = 0;
> +                               break;
> +                       }
> +
> +                       if (offset + step_payload_len > buf_len) {
> +                               DBG(rap, "Insufficient data for "
> +                                       "step %u payload (need %zu, have %zu)",
> +                                       step_idx, step_payload_len,
> +                                       buf_len - offset);
> +                               break;
> +                       }
> +
> +                       DBG(rap, "  Step %u: mode=%u payload_len=%zu",
> +                           step_idx, step_mode, step_payload_len);
> +
> +                       /* Parse mode data if payload is present */
> +                       if (step_payload_len > 0) {
> +                               const uint8_t *payload_ptr = buf + offset;
> +                               struct iovec mode_iov;
> +
> +                               mode_iov.iov_base = (void *)payload_ptr;
> +                               mode_iov.iov_len = step_payload_len;
> +
> +                               switch (step_mode) {
> +                               case CS_MODE_ZERO:
> +                                       parse_mode_zero(rap, &mode_iov,
> +                                                       remote_role);
> +                                       break;
> +                               case CS_MODE_ONE:
> +                                       parse_mode_one(rap, &mode_iov,
> +                                               remote_role, include_pct);
> +                                       break;
> +                               case CS_MODE_TWO:
> +                                       parse_mode_two(rap, &mode_iov);
> +                                       break;
> +                               case CS_MODE_THREE:
> +                                       parse_mode_three(rap, &mode_iov,
> +                                               remote_role, include_pct);
> +                                       break;
> +                               default:
> +                                       break;
> +                               }
> +
> +                               /* Skip payload */
> +                               offset += step_payload_len;
> +                       }
> +               }
> +
> +               /* Check if this is the last subevent */
> +               if (subevt_hdr.subevent_done_status ==
> +                   SUBEVENT_DONE_ALL_RESULTS_COMPLETE ||
> +                   subevt_hdr.ranging_done_status ==
> +                   RANGING_DONE_ALL_RESULTS_COMPLETE) {
> +                       DBG(rap, "Ranging procedure complete");
> +                       break;
> +               }
> +       }
> +
> +       /* Clean up accumulator */
> +       free(reqtracker->segment_data.iov_base);
> +       reqtracker->segment_data.iov_base = NULL;
> +       reqtracker->segment_data.iov_len = 0;
> +}
> +
> +static bool process_first_segment(struct bt_rap *rap,
> +                                       struct cstracker *reqtracker,
> +                                       struct iovec *iov, bool last_segment)
> +{
> +       uint16_t counter_config_val;
> +       int8_t selected_tx_power;
> +       uint8_t antenna_pct;
> +
> +       if (!util_iov_pull_le16(iov, &counter_config_val) ||
> +           !util_iov_pull_u8(iov, (uint8_t *)&selected_tx_power) ||
> +           !util_iov_pull_u8(iov, &antenna_pct)) {
> +               DBG(rap, "First segment too short for ranging header");
> +               return false;
> +       }
> +
> +       ranging_header_set_counter(&reqtracker->ranging_header_,
> +                               counter_config_val & 0x0FFF);
> +       ranging_header_set_config_id(&reqtracker->ranging_header_,
> +                               (counter_config_val >> 12) & 0x0F);
> +       reqtracker->ranging_header_.selected_tx_power = selected_tx_power;
> +       reqtracker->ranging_header_.antenna_pct = antenna_pct;
> +
> +       DBG(rap, "First segment: parsed ranging header "
> +           "(counter=%u, config_id=%u, tx_pwr=%d)",
> +           counter_config_val & 0x0FFF,
> +           (counter_config_val >> 12) & 0x0F,
> +           selected_tx_power);
> +
> +       if (reqtracker->segment_data.iov_base) {
> +               free(reqtracker->segment_data.iov_base);
> +               reqtracker->segment_data.iov_base = NULL;
> +               reqtracker->segment_data.iov_len = 0;
> +       }
> +
> +       if (iov->iov_len > 0) {
> +               if (!util_iov_append(&reqtracker->segment_data,
> +                                       iov->iov_base, iov->iov_len)) {
> +                       DBG(rap, "Failed to initialize segment accumulator");
> +                       return false;
> +               }
> +               DBG(rap, "First segment: initialized accumulator "
> +                   "with %zu bytes", iov->iov_len);
> +       }
> +
> +       if (!last_segment)
> +               return false;
> +
> +       DBG(rap, "Single-segment: first=1, last=1");
> +       return true;
> +}
> +
> +static void ras_realtime_notify_cb(struct bt_rap *rap, uint16_t value_handle,
> +                                  const uint8_t *value, uint16_t length,
> +                                  void *user_data)
> +{
> +       struct iovec iov = { .iov_base = (void *)value, .iov_len = length };
> +       struct segmentation_header seg_hdr;
> +       struct cstracker *reqtracker;
> +
> +       if (!rap || !value || length == 0) {
> +               DBG(rap, "Invalid notification data");
> +               return;
> +       }
> +
> +       DBG(rap, "Received real-time notification: handle=0x%04x len=%u",
> +           value_handle, length);
> +
> +       if (!parse_segmentation_header(&iov, &seg_hdr)) {
> +               DBG(rap, "Failed to parse segmentation header");
> +               return;
> +       }
> +
> +       DBG(rap, "Segment: first=%u last=%u counter=%u",
> +           seg_hdr.first_segment, seg_hdr.last_segment,
> +           seg_hdr.rolling_segment_counter);
> +
> +       if (!rap->reqtracker) {
> +               DBG(rap, "reqtracker is not initialised");
> +               return;
> +       }
> +
> +       reqtracker = rap->reqtracker;
> +
> +       if (seg_hdr.first_segment) {
> +               if (!process_first_segment(rap, reqtracker, &iov,
> +                                               seg_hdr.last_segment))
> +                       return;
> +       } else {
> +               if (iov.iov_len > 0) {
> +                       if (!util_iov_append(&reqtracker->segment_data,
> +                                               iov.iov_base, iov.iov_len)) {
> +                               DBG(rap, "Failed to append segment data");
> +                               return;
> +                       }
> +                       DBG(rap, "Continuation segment: appended "
> +                           "%zu bytes (total=%zu)",
> +                           iov.iov_len,
> +                           reqtracker->segment_data.iov_len);
> +               }
> +       }
> +
> +       /* Last segment: parse complete RAS data */
> +       if (seg_hdr.last_segment)
> +               parse_ras_data_segments(rap, reqtracker);
> +}
> +
> +static void read_ras_features(struct bt_rap *rap, bool success,
> +                       uint8_t att_ecode,
> +                       const uint8_t *value, uint16_t length,
> +                       void *user_data)
> +{
> +       struct iovec iov = { .iov_base = (void *)value, .iov_len = length };
> +       uint32_t features = 0;
> +       struct ras *ras;
> +       bool supports_realtime;
> +       bool retrieve_lost;
> +       bool abort_operation;
> +
> +       if (!success) {
> +               DBG(rap, "Unable to read RAS Features: error 0x%02x",
> +                       att_ecode);
> +               return;
> +       }
> +
> +       ras = rap_get_ras(rap);
> +
> +       if (length == 0 || length > 4) {
> +               DBG(rap, "Invalid RAS Features length: %u (expected 1-4)",
> +                       length);
> +               return;
> +       }
> +
> +       if (length < 4) {
> +               uint8_t padded[4] = { 0, 0, 0, 0 };
> +
> +               memcpy(padded, value, length);
> +               iov.iov_base = padded;
> +               iov.iov_len = 4;
> +               DBG(rap, "RAS Features: short read (%u bytes), zero-pad to 4",
> +                   length);
> +       }
> +
> +       if (!util_iov_pull_le32(&iov, &features)) {
> +               DBG(rap, "Unable to parse RAS Features value");
> +               return;
> +       }
> +
> +       DBG(rap, "RAS Features: 0x%08x", features);
> +
> +       supports_realtime = (features & 0x01) != 0;
> +       retrieve_lost = (features & 0x02) != 0;
> +       abort_operation = (features & 0x04) != 0;
> +
> +       DBG(rap, "RAS Features - Real-time: %s, Retrieve Lost: %s, Abort: %s",
> +           supports_realtime ? "Yes" : "No",
> +           retrieve_lost ? "Yes" : "No",
> +           abort_operation ? "Yes" : "No");
> +
> +       DBG(rap, "RAS features read successfully, capabilities determined");
> +
> +       /* Register for real-time characteristic notifications if supported */
> +       if (supports_realtime && ras && ras->realtime_chrc && rap->client) {
> +               uint16_t value_handle;
> +               bt_uuid_t uuid;
> +
> +               if (gatt_db_attribute_get_char_data(ras->realtime_chrc,
> +                                       NULL, &value_handle,
> +                                       NULL, NULL, &uuid)) {
> +                       unsigned int notify_id;
> +
> +                       notify_id = bt_rap_register_notify(rap,
> +                                               value_handle,
> +                                               ras_realtime_notify_cb,
> +                                               NULL);
> +                       if (!notify_id)
> +                               DBG(rap, "Failed to register for "
> +                                       "real-time notifications");
> +                       else
> +                               DBG(rap, "Registered for real-time "
> +                                       "features: id=%u", notify_id);
> +               }
> +       } else {
> +               DBG(rap, "On demand ranging "
> +                       "remote device - skipping notification "
> +                       "registration");
> +       }
> +}
> +
>  static void foreach_rap_char(struct gatt_db_attribute *attr, void *user_data)
>  {
>         struct bt_rap *rap = user_data;
> @@ -1848,6 +2807,10 @@ static void foreach_rap_char(struct gatt_db_attribute *attr, void *user_data)
>                         return;
>
>                 ras->feat_chrc = attr;
> +               if (rap->client) {
> +                       rap_read_value(rap, value_handle,
> +                                       read_ras_features, rap);
> +               }
>         }
>
>         if (!bt_uuid_cmp(&uuid, &uuid_realtime)) {
> --
> 2.34.1
>


-- 
Luiz Augusto von Dentz

^ permalink raw reply

* Re: [PATCH v7 RESEND 0/1] Bluetooth: L2CAP: Fix slab-use-after-free in l2cap_sock_cleanup_listen()
From: Luiz Augusto von Dentz @ 2026-05-20 18:26 UTC (permalink / raw)
  To: Siwei Zhang; +Cc: Marcel Holtmann, linux-bluetooth, Safa Karakuş
In-Reply-To: <20260520163859.2859782-1-oss@fourdim.xyz>

Hi Siwei,

On Wed, May 20, 2026 at 12:39 PM Siwei Zhang <oss@fourdim.xyz> wrote:
>
> Hi Bluetooth maintainers,
>
> A public patch covering the same UAF in l2cap_sock_cleanup_listen() was posted to linux-bluetooth on April 28
> by Safa Karakuş. v4 is here:
>
> https://lore.kernel.org/linux-bluetooth/AS8P250MB079109F82C16BEDC4F9FE584EB372@AS8P250MB0791.EURP250.PROD.OUTLOOK.COM/
>
> I thanks for Safa's report and patch. I already reported the same issue privately to the maintainers in
> April 11th. The public patch breaks the embargo and I would like to resend my patch here.
>
> Safa's v4 closes the sk-lifetime hole (sock_hold inside bt_accept_dequeue) but does not take conn->lock around
> l2cap_chan_close, so the conn->chan_l list-corruption race in my report is still open after it.

Are your changes on top of Safa's though? That seems a lot cleaner to be honest.

> My patch closes both: it drops the parent sk_lock, acquires conn->lock → chan->lock in the established order
> to serialize the chan_l mutation, and re-takes the parent sk_lock before returning.

I rather have each issue handled separately though.

> Crash stack and C reproducers are available upon request, only for the maintainers.
>
> Maintainers can also refer to the email thread [Bug] KASAN: slab-use-after-free Read in l2cap_security_cfm
> sent to security@kernel.org on April 11th for more details.
>
> Detailed Timeline:
>
> April 11th: I privately reported the issue to the maintainers and security@kernel.org
> April 12th: Patch v1
> April 13th: Patch v2
> April 13th: Patch v3
> April 14th: Patch v4
> April 15th: Patch v5
> May 2nd: Patch v6
> May 2nd: Patch v7
> May 20th: Resend v7 with a cover letter
>
> Best,
> Siwei
>
> Siwei Zhang (1):
>   Bluetooth: L2CAP: Fix slab-use-after-free in
>     l2cap_sock_cleanup_listen()
>
>  net/bluetooth/l2cap_sock.c | 57 ++++++++++++++++++++++++++++++++------
>  1 file changed, 49 insertions(+), 8 deletions(-)
>
> --
> 2.54.0
>


-- 
Luiz Augusto von Dentz

^ permalink raw reply

* [bluez/bluez] ea64b0: shared/rap: Add client real-time ranging registrat...
From: prathibhamadugonde @ 2026-05-20 18:42 UTC (permalink / raw)
  To: linux-bluetooth

  Branch: refs/heads/1098168
  Home:   https://github.com/bluez/bluez
  Commit: ea64b06fc351ced86319a789e5bde3506c3751c6
      https://github.com/bluez/bluez/commit/ea64b06fc351ced86319a789e5bde3506c3751c6
  Author: Prathibha Madugonde <prathibha.madugonde@oss.qualcomm.com>
  Date:   2026-05-20 (Wed, 20 May 2026)

  Changed paths:
    M src/shared/rap.c

  Log Message:
  -----------
  shared/rap: Add client real-time ranging registration and notification parsing

Read the RAS Features characteristic to determine whether the remote
device supports real-time ranging. If supported, register for real-time
characteristic notifications using the reqtracker for the CS initiator
role.

Parse incoming segmented RAS ranging data notifications by accumulating
segments via iovec and parsing complete subevent headers and CS mode 0-3
step data, including IQ/tone PCT samples, once the last segment arrives.



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

^ permalink raw reply

* RE: [BlueZ,v1] shared/rap: Add client real-time ranging registration and notification parsing
From: bluez.test.bot @ 2026-05-20 18:48 UTC (permalink / raw)
  To: linux-bluetooth, prathibha.madugonde
In-Reply-To: <20260520163037.1823823-1-prathm@qti.qualcomm.com>

[-- Attachment #1: Type: text/plain, Size: 10602 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=1098168

---Test result---

Test Summary:
CheckPatch                    PASS      0.79 seconds
GitLint                       FAIL      0.23 seconds
BuildEll                      PASS      15.85 seconds
BluezMake                     FAIL      15.98 seconds
MakeCheck                     FAIL      34.43 seconds
MakeDistcheck                 FAIL      173.16 seconds
CheckValgrind                 FAIL      12.94 seconds
CheckSmatch                   FAIL      20.21 seconds
bluezmakeextell               FAIL      11.07 seconds
IncrementalBuild              FAIL      16.48 seconds
ScanBuild                     FAIL      26.70 seconds

Details
##############################
Test: GitLint - FAIL
Desc: Run gitlint
Output:
[BlueZ,v1] shared/rap: Add client real-time ranging registration and notification parsing

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
1: T1 Title exceeds max length (89>80): "[BlueZ,v1] shared/rap: Add client real-time ranging registration and notification parsing"
##############################
Test: BluezMake - FAIL
Desc: Build BlueZ
Output:

src/shared/rap.c: In function ‘fill_initiator_data_from_cs_subevent_result_cont’:
src/shared/rap.c:1788:20: error: variable ‘reqtracker’ set but not used [-Werror=unused-but-set-variable]
 1788 |  struct cstracker *reqtracker;
      |                    ^~~~~~~~~~
cc1: all warnings being treated as errors
make[1]: *** [Makefile:7859: src/shared/libshared_mainloop_la-rap.lo] Error 1
make[1]: *** Waiting for unfinished jobs....
make: *** [Makefile:4172: all] Error 2
##############################
Test: MakeCheck - FAIL
Desc: Run Bluez Make Check
Output:

src/shared/rap.c: In function ‘fill_initiator_data_from_cs_subevent_result_cont’:
src/shared/rap.c:1788:20: error: variable ‘reqtracker’ set but not used [-Werror=unused-but-set-variable]
 1788 |  struct cstracker *reqtracker;
      |                    ^~~~~~~~~~
cc1: all warnings being treated as errors
make[1]: *** [Makefile:7579: src/shared/libshared_glib_la-rap.lo] Error 1
make: *** [Makefile:10820: check] Error 2
##############################
Test: MakeDistcheck - FAIL
Desc: Run Bluez Make Distcheck
Output:

make[4]: *** [Makefile:10239: test-suite.log] Error 1
make[3]: *** [Makefile:10347: check-TESTS] Error 2
make[2]: *** [Makefile:10818: check-am] Error 2
make[1]: *** [Makefile:10820: check] Error 2
make: *** [Makefile:10741: distcheck] Error 1
##############################
Test: CheckValgrind - FAIL
Desc: Run Bluez Make Check with Valgrind
Output:

src/shared/rap.c: In function ‘fill_initiator_data_from_cs_subevent_result_cont’:
src/shared/rap.c:1788:20: error: variable ‘reqtracker’ set but not used [-Werror=unused-but-set-variable]
 1788 |  struct cstracker *reqtracker;
      |                    ^~~~~~~~~~
cc1: all warnings being treated as errors
make[1]: *** [Makefile:7859: src/shared/libshared_mainloop_la-rap.lo] Error 1
make[1]: *** Waiting for unfinished jobs....
make: *** [Makefile:10820: check] Error 2
##############################
Test: CheckSmatch - FAIL
Desc: Run smatch tool with source
Output:

src/shared/crypto.c:271:21: warning: Variable length array is used.
src/shared/crypto.c:272:23: warning: Variable length array is used.
src/shared/gatt-helpers.c:764:31: warning: Variable length array is used.
src/shared/gatt-helpers.c:842:31: warning: Variable length array is used.
src/shared/gatt-helpers.c:1335:31: warning: Variable length array is used.
src/shared/gatt-helpers.c:1366:23: 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/bap.c:312:25: warning: array of flexible structures
src/shared/bap.c: note: in included file:
./src/shared/ascs.h:88:25: warning: array of flexible structures
src/shared/shell.c: note: in included file (through /usr/include/readline/readline.h):
/usr/include/readline/rltypedefs.h:35:23: warning: non-ANSI function declaration of function 'Function'
/usr/include/readline/rltypedefs.h:36:25: warning: non-ANSI function declaration of function 'VFunction'
/usr/include/readline/rltypedefs.h:37:27: warning: non-ANSI function declaration of function 'CPFunction'
/usr/include/readline/rltypedefs.h:38:29: warning: non-ANSI function declaration of function 'CPPFunction'
src/shared/rap.c: In function ‘fill_initiator_data_from_cs_subevent_result_cont’:
src/shared/rap.c:1788:20: error: variable ‘reqtracker’ set but not used [-Werror=unused-but-set-variable]
 1788 |  struct cstracker *reqtracker;
      |                    ^~~~~~~~~~
cc1: all warnings being treated as errors
make[1]: *** [Makefile:7859: src/shared/libshared_mainloop_la-rap.lo] Error 1
make[1]: *** Waiting for unfinished jobs....
src/shared/crypto.c:271:21: warning: Variable length array is used.
src/shared/crypto.c:272:23: warning: Variable length array is used.
make: *** [Makefile:4172: all] Error 2
##############################
Test: bluezmakeextell - FAIL
Desc: Build Bluez with External ELL
Output:

src/shared/rap.c: In function ‘fill_initiator_data_from_cs_subevent_result_cont’:
src/shared/rap.c:1788:20: error: variable ‘reqtracker’ set but not used [-Werror=unused-but-set-variable]
 1788 |  struct cstracker *reqtracker;
      |                    ^~~~~~~~~~
cc1: all warnings being treated as errors
make[1]: *** [Makefile:7859: src/shared/libshared_mainloop_la-rap.lo] Error 1
make[1]: *** Waiting for unfinished jobs....
make: *** [Makefile:4172: all] Error 2
##############################
Test: IncrementalBuild - FAIL
Desc: Incremental build with the patches in the series
Output:

src/shared/rap.c: In function ‘fill_initiator_data_from_cs_subevent_result_cont’:
src/shared/rap.c:1788:20: error: variable ‘reqtracker’ set but not used [-Werror=unused-but-set-variable]
 1788 |  struct cstracker *reqtracker;
      |                    ^~~~~~~~~~
cc1: all warnings being treated as errors
make[1]: *** [Makefile:7859: src/shared/libshared_mainloop_la-rap.lo] Error 1
make[1]: *** Waiting for unfinished jobs....
make: *** [Makefile:4172: all] Error 2
[BlueZ,v1] shared/rap: Add client real-time ranging registration and notification parsing

src/shared/rap.c: In function ‘fill_initiator_data_from_cs_subevent_result_cont’:
src/shared/rap.c:1788:20: error: variable ‘reqtracker’ set but not used [-Werror=unused-but-set-variable]
 1788 |  struct cstracker *reqtracker;
      |                    ^~~~~~~~~~
cc1: all warnings being treated as errors
make[1]: *** [Makefile:7859: src/shared/libshared_mainloop_la-rap.lo] Error 1
make[1]: *** Waiting for unfinished jobs....
make: *** [Makefile:4172: all] Error 2
##############################
Test: ScanBuild - FAIL
Desc: Run Scan Build
Output:

src/shared/gatt-client.c:447:21: warning: Use of memory after it is freed
        gatt_db_unregister(op->client->db, op->db_id);
                           ^~~~~~~~~~
src/shared/gatt-client.c:692:2: warning: Use of memory after it is freed
        discovery_op_complete(op, false, att_ecode);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:992:2: warning: Use of memory after it is freed
        discovery_op_complete(op, success, att_ecode);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1098:2: warning: Use of memory after it is freed
        discovery_op_complete(op, success, att_ecode);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1292:2: warning: Use of memory after it is freed
        discovery_op_complete(op, success, att_ecode);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1357:2: warning: Use of memory after it is freed
        discovery_op_complete(op, success, att_ecode);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1632:6: warning: Use of memory after it is freed
        if (read_db_hash(op)) {
            ^~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1637:2: warning: Use of memory after it is freed
        discover_all(op);
        ^~~~~~~~~~~~~~~~
src/shared/gatt-client.c:1693:56: warning: Use of memory after it is freed
        notify_data->chrc->ccc_write_id = notify_data->att_id = att_id;
                                          ~~~~~~~~~~~~~~~~~~~ ^
src/shared/gatt-client.c:2146:6: warning: Use of memory after it is freed
        if (read_db_hash(op)) {
            ^~~~~~~~~~~~~~~~
src/shared/gatt-client.c:2154:8: warning: Use of memory after it is freed
                                                        discovery_op_ref(op),
                                                        ^~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:3332:2: warning: Use of memory after it is freed
        complete_write_long_op(req, success, 0, false);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
src/shared/gatt-client.c:3354:2: warning: Use of memory after it is freed
        request_unref(req);
        ^~~~~~~~~~~~~~~~~~
13 warnings generated.
src/shared/rap.c: In function ‘fill_initiator_data_from_cs_subevent_result_cont’:
src/shared/rap.c:1788:20: error: variable ‘reqtracker’ set but not used [-Werror=unused-but-set-variable]
 1788 |  struct cstracker *reqtracker;
      |                    ^~~~~~~~~~
cc1: all warnings being treated as errors
make[1]: *** [Makefile:7859: src/shared/libshared_mainloop_la-rap.lo] Error 1
make[1]: *** Waiting for unfinished jobs....
src/shared/bap.c:1529:8: warning: Use of memory after it is freed
        bap = bt_bap_ref_safe(bap);
              ^~~~~~~~~~~~~~~~~~~~
src/shared/bap.c:2340:20: warning: Use of memory after it is freed
        return queue_find(stream->bap->streams, NULL, stream);
                          ^~~~~~~~~~~~~~~~~~~~
2 warnings generated.
make: *** [Makefile:4172: all] Error 2


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

---
Regards,
Linux Bluetooth


^ permalink raw reply

* Re: [PATCH v7 RESEND 0/1] Bluetooth: L2CAP: Fix slab-use-after-free in l2cap_sock_cleanup_listen()
From: Siwei Zhang @ 2026-05-20 18:56 UTC (permalink / raw)
  To: Luiz Augusto von Dentz
  Cc: Marcel Holtmann, linux-bluetooth, Safa Karakuş
In-Reply-To: <CABBYNZ+6bP78+_m_ehhif3xCXxn2ZLQE2q5O3X_03mU8=_T5uA@mail.gmail.com>

Hi Luiz,

On Wed, May 20, 2026, at 2:26 PM, Luiz Augusto von Dentz wrote:
> Hi Siwei,
>
> On Wed, May 20, 2026 at 12:39 PM Siwei Zhang <oss@fourdim.xyz> wrote:
>>
>> Hi Bluetooth maintainers,
>>
>> A public patch covering the same UAF in l2cap_sock_cleanup_listen() was posted to linux-bluetooth on April 28
>> by Safa Karakuş. v4 is here:
>>
>> https://lore.kernel.org/linux-bluetooth/AS8P250MB079109F82C16BEDC4F9FE584EB372@AS8P250MB0791.EURP250.PROD.OUTLOOK.COM/
>>
>> I thanks for Safa's report and patch. I already reported the same issue privately to the maintainers in
>> April 11th. The public patch breaks the embargo and I would like to resend my patch here.
>>
>> Safa's v4 closes the sk-lifetime hole (sock_hold inside bt_accept_dequeue) but does not take conn->lock around
>> l2cap_chan_close, so the conn->chan_l list-corruption race in my report is still open after it.
>
> Are your changes on top of Safa's though? That seems a lot cleaner to be honest.
>

My patch is not on the top of Safa's. The diff looks quite different.
I reported both the sk-lifetime UAF and the conn->chan_l list-corruption race
privately to the maintainers on April 11th. And patch shortly on April 12th.

>> My patch closes both: it drops the parent sk_lock, acquires conn->lock → chan->lock in the established order
>> to serialize the chan_l mutation, and re-takes the parent sk_lock before returning.
>
> I rather have each issue handled separately though.
>

I am happy to handle that separately.

Could I get a Reported-by on Safa's patch since I reported the underlying issue before the public post?

Reported-by: Siwei Zhang <oss@fourdim.xyz>

I'll send the conn->lock patch (drains accept queue to local list, drops parent sk_lock, acquires conn->lock -> chan_lock in
established order) as another patch shortly.

>> Crash stack and C reproducers are available upon request, only for the maintainers.
>>
>> Maintainers can also refer to the email thread [Bug] KASAN: slab-use-after-free Read in l2cap_security_cfm
>> sent to security@kernel.org on April 11th for more details.
>>
>> Detailed Timeline:
>>
>> April 11th: I privately reported the issue to the maintainers and security@kernel.org
>> April 12th: Patch v1
>> April 13th: Patch v2
>> April 13th: Patch v3
>> April 14th: Patch v4
>> April 15th: Patch v5
>> May 2nd: Patch v6
>> May 2nd: Patch v7
>> May 20th: Resend v7 with a cover letter
>>
>> Best,
>> Siwei
>>
>> Siwei Zhang (1):
>>   Bluetooth: L2CAP: Fix slab-use-after-free in
>>     l2cap_sock_cleanup_listen()
>>
>>  net/bluetooth/l2cap_sock.c | 57 ++++++++++++++++++++++++++++++++------
>>  1 file changed, 49 insertions(+), 8 deletions(-)
>>
>> --
>> 2.54.0
>>
>
>
> -- 
> Luiz Augusto von Dentz

Best,
Siwei


^ permalink raw reply

* Re: [PATCH v4] Bluetooth: fix UAF in l2cap_sock_cleanup_listen() vs l2cap_conn_del()
From: fourdim @ 2026-05-20 19:14 UTC (permalink / raw)
  To: Safa Karakuş, linux-bluetooth
  Cc: Luiz Augusto von Dentz, Marcel Holtmann, stable, linux-kernel
In-Reply-To: <20260516181504.3076260-1-safa.karakus@secunnix.com>

Hi,

On Sat, May 16, 2026, at 2:15 PM, Safa Karakuş wrote:
> bt_accept_dequeue() unlinks a not-yet-accepted child from the parent
> accept queue and release_sock()s it before returning, so the returned
> sk has no caller reference and is unlocked.
>
> l2cap_sock_cleanup_listen() walks these children on listening-socket
> close.  A concurrent HCI disconnect drives hci_rx_work ->
> l2cap_conn_del() which runs l2cap_chan_del() + l2cap_sock_kill() and
> frees the child sk and its l2cap_chan; cleanup_listen() then uses both:
>
>   BUG: KASAN: slab-use-after-free in l2cap_sock_kill
>     l2cap_sock_kill / l2cap_sock_cleanup_listen / __x64_sys_close
>   Freed by: l2cap_conn_del -> l2cap_sock_close_cb -> l2cap_sock_kill
>
> This is distinct from the two fixes already in this area: commit
> e83f5e24da741 ("Bluetooth: serialize accept_q access") serialises the
> accept_q list/poll and takes temporary refs inside bt_accept_dequeue(),
> and CVE-2025-39860 serialises the userspace close()/accept() race by
> calling cleanup_listen() under lock_sock() in l2cap_sock_release().
> Neither covers l2cap_conn_del() running from hci_rx_work, so this UAF
> still reproduces on current bluetooth/master.
>
> Take the reference at the source: bt_accept_dequeue() does sock_hold()
> while sk is still locked, before release_sock(); callers sock_put().
> cleanup_listen() pins the chan with l2cap_chan_hold_unless_zero() under
> a brief child sk lock (serialising vs l2cap_sock_teardown_cb()), drops
> it before l2cap_chan_lock(), and skips a duplicate l2cap_sock_kill() on
> SOCK_DEAD.  conn->lock is not taken here: cleanup_listen() runs under
> the parent sk lock and that would invert
> conn->lock -> chan->lock -> sk_lock (lockdep).
>
> KASAN/SMP: an unprivileged listen/close vs HCI-disconnect race produced
> 12 use-after-free reports per run before this change; 0, and no lockdep
> report, over 1600+ raced iterations after it on bluetooth/master.
>
> Fixes: 15f02b910562 ("Bluetooth: L2CAP: Add initial code for Enhanced 
> Credit Based Mode")
> Cc: stable@vger.kernel.org
> Signed-off-by: Safa Karakuş <safa.karakus@secunnix.com>
> ---
> Hi Luiz,
>
> v4 - rebased on current bluetooth/master (after e83f5e24d "serialize
> accept_q access"); the af_bluetooth.c hunk now sits in the reworked
> bt_accept_dequeue().  This residual (cleanup_listen vs l2cap_conn_del,
> not covered by e83f5e24d nor CVE-2025-39860) is unchanged from v3 and
> re-verified with KASAN on bluetooth/master: 12 UAF/run -> 0, no
> lockdep, over 1600+ raced iterations.
>
> Changes since v3: rebased onto e83f5e24d; commit message notes why
> e83f5e24d/CVE-2025-39860 do not cover this path.
> Changes since v2: fix at the source in bt_accept_dequeue() + chan
> lifetime via l2cap_chan_hold_unless_zero(); no conn->lock (lockdep).
> Changes since v1: consistent From/Signed-off-by.
>
>  net/bluetooth/af_bluetooth.c | 10 +++++++
>  net/bluetooth/iso.c          |  9 ++++++-
>  net/bluetooth/l2cap_sock.c   | 51 +++++++++++++++++++++++++++++++-----
>  net/bluetooth/rfcomm/sock.c  |  9 ++++++-
>  net/bluetooth/sco.c          |  9 ++++++-
>  5 files changed, 78 insertions(+), 10 deletions(-)
>
> diff --git a/net/bluetooth/af_bluetooth.c b/net/bluetooth/af_bluetooth.c
> index 9d68dd860..1a6aa3f8d 100644
> --- a/net/bluetooth/af_bluetooth.c
> +++ b/net/bluetooth/af_bluetooth.c
> @@ -340,6 +340,16 @@ struct sock *bt_accept_dequeue(struct sock 
> *parent, struct socket *newsock)
>  			if (newsock)
>  				sock_graft(sk, newsock);
> 
> +			/* Hand the caller a reference taken while sk is
> +			 * still locked.  bt_accept_unlink() just dropped
> +			 * the accept-queue reference; without this hold a
> +			 * concurrent teardown (e.g. l2cap_conn_del() ->
> +			 * l2cap_sock_kill()) could free sk between
> +			 * release_sock() and the caller using it.  Every
> +			 * caller drops this with sock_put() when done.
> +			 */
> +			sock_hold(sk);
> +
>  			release_sock(sk);
>  			if (next)
>  				sock_put(next);
> diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c
> index 7cb2864fe..812f9002d 100644
> --- a/net/bluetooth/iso.c
> +++ b/net/bluetooth/iso.c
> @@ -751,6 +751,8 @@ static void iso_sock_cleanup_listen(struct sock *parent)
>  	while ((sk = bt_accept_dequeue(parent, NULL))) {
>  		iso_sock_close(sk);
>  		iso_sock_kill(sk);
> +		/* Drop the reference handed back by bt_accept_dequeue(). */
> +		sock_put(sk);
>  	}
> 
>  	/* If listening socket has a hcon, properly disconnect it */
> @@ -1356,8 +1358,13 @@ static int iso_sock_accept(struct socket *sock, 
> struct socket *newsock,
>  		}
> 
>  		ch = bt_accept_dequeue(sk, newsock);
> -		if (ch)
> +		if (ch) {
> +			/* Drop the bridging ref from bt_accept_dequeue();
> +			 * the grafted socket keeps ch alive from here.
> +			 */
> +			sock_put(ch);
>  			break;
> +		}
> 
>  		if (!timeo) {
>  			err = -EAGAIN;
> diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
> index cf590a67d..b34e7da8d 100644
> --- a/net/bluetooth/l2cap_sock.c
> +++ b/net/bluetooth/l2cap_sock.c
> @@ -349,8 +349,13 @@ static int l2cap_sock_accept(struct socket *sock, 
> struct socket *newsock,
>  		}
> 
>  		nsk = bt_accept_dequeue(sk, newsock);
> -		if (nsk)
> +		if (nsk) {
> +			/* Drop the bridging ref from bt_accept_dequeue();
> +			 * the grafted socket keeps nsk alive from here.
> +			 */
> +			sock_put(nsk);
>  			break;
> +		}
> 
>  		if (!timeo) {
>  			err = -EAGAIN;
> @@ -1475,22 +1480,54 @@ static void l2cap_sock_cleanup_listen(struct 
> sock *parent)
>  	BT_DBG("parent %p state %s", parent,
>  	       state_to_string(parent->sk_state));
> 
> -	/* Close not yet accepted channels */
> +	/* Close not yet accepted channels.
> +	 *
> +	 * bt_accept_dequeue() now returns sk with an extra reference held
> +	 * (taken while sk was still locked) so a concurrent l2cap_conn_del()
> +	 * -> l2cap_sock_kill() cannot free sk under us.
> +	 *
> +	 * cleanup_listen() runs under the parent sk lock, so unlike
> +	 * l2cap_sock_shutdown() we must NOT take conn->lock here: that would
> +	 * establish sk_lock -> conn->lock and invert the established
> +	 * conn->lock -> chan->lock -> sk_lock order (lockdep deadlock).
> +	 *
> +	 * Instead, briefly take the child sk lock to fetch and pin its chan.
> +	 * l2cap_conn_del() reaches the chan free only via
> +	 * l2cap_chan_del() -> l2cap_sock_teardown_cb(), which itself takes
> +	 * the child sk lock; holding it across l2cap_chan_hold_unless_zero()
> +	 * therefore guarantees the chan cannot be freed while we read and
> +	 * pin it (hold_unless_zero() additionally skips a chan already past
> +	 * its last reference).  We then drop the sk lock before taking
> +	 * chan->lock, so sk and chan locks are never held together.
> +	 */
>  	while ((sk = bt_accept_dequeue(parent, NULL))) {
> -		struct l2cap_chan *chan = l2cap_pi(sk)->chan;
> +		struct l2cap_chan *chan;
> +
> +		lock_sock_nested(sk, L2CAP_NESTING_NORMAL);
> +		chan = l2cap_chan_hold_unless_zero(l2cap_pi(sk)->chan);
> +		release_sock(sk);
> +		if (!chan) {
> +			/* l2cap_conn_del() already tearing this child down */
> +			sock_put(sk);
> +			continue;
> +		}
> 
>  		BT_DBG("child chan %p state %s", chan,
>  		       state_to_string(chan->state));
> 
> -		l2cap_chan_hold(chan);
>  		l2cap_chan_lock(chan);
> -
>  		__clear_chan_timer(chan);
>  		l2cap_chan_close(chan, ECONNRESET);
> -		l2cap_sock_kill(sk);
> -
> +		/* l2cap_conn_del() may already have killed this socket
> +		 * (it sets SOCK_DEAD); skip the duplicate to avoid a
> +		 * double sock_put()/l2cap_chan_put().
> +		 */
> +		if (!sock_flag(sk, SOCK_DEAD))
> +			l2cap_sock_kill(sk);
>  		l2cap_chan_unlock(chan);
> +
>  		l2cap_chan_put(chan);
> +		sock_put(sk);
>  	}
>  }
> 
> diff --git a/net/bluetooth/rfcomm/sock.c b/net/bluetooth/rfcomm/sock.c
> index be6639cd6..bd7d959c6 100644
> --- a/net/bluetooth/rfcomm/sock.c
> +++ b/net/bluetooth/rfcomm/sock.c
> @@ -180,6 +180,8 @@ static void rfcomm_sock_cleanup_listen(struct sock *parent)
>  	while ((sk = bt_accept_dequeue(parent, NULL))) {
>  		rfcomm_sock_close(sk);
>  		rfcomm_sock_kill(sk);
> +		/* Drop the reference handed back by bt_accept_dequeue(). */
> +		sock_put(sk);
>  	}
> 
>  	parent->sk_state  = BT_CLOSED;
> @@ -497,8 +499,13 @@ static int rfcomm_sock_accept(struct socket *sock, 
> struct socket *newsock,
>  		}
> 
>  		nsk = bt_accept_dequeue(sk, newsock);
> -		if (nsk)
> +		if (nsk) {
> +			/* Drop the bridging ref from bt_accept_dequeue();
> +			 * the grafted socket keeps nsk alive from here.
> +			 */
> +			sock_put(nsk);
>  			break;
> +		}
> 
>  		if (!timeo) {
>  			err = -EAGAIN;
> diff --git a/net/bluetooth/sco.c b/net/bluetooth/sco.c
> index eba44525d..f1799c6a6 100644
> --- a/net/bluetooth/sco.c
> +++ b/net/bluetooth/sco.c
> @@ -502,6 +502,8 @@ static void sco_sock_cleanup_listen(struct sock *parent)
>  	while ((sk = bt_accept_dequeue(parent, NULL))) {
>  		sco_sock_close(sk);
>  		sco_sock_kill(sk);
> +		/* Drop the reference handed back by bt_accept_dequeue(). */
> +		sock_put(sk);
>  	}
> 
>  	parent->sk_state  = BT_CLOSED;
> @@ -765,8 +767,13 @@ static int sco_sock_accept(struct socket *sock, 
> struct socket *newsock,
>  		}
> 
>  		ch = bt_accept_dequeue(sk, newsock);
> -		if (ch)
> +		if (ch) {
> +			/* Drop the bridging ref from bt_accept_dequeue();
> +			 * the grafted socket keeps ch alive from here.
> +			 */
> +			sock_put(ch);
>  			break;
> +		}
> 
>  		if (!timeo) {
>  			err = -EAGAIN;
> -- 
> 2.34.1

I reported the same issue privately to the maintainers on April 11th.
The fix looks correct for the sk-lifetime UAF.

Reported-by: Siwei Zhang <oss@fourdim.xyz>
Reviewed-by: Siwei Zhang <oss@fourdim.xyz>

This patch leaves the conn->chan_l list-corruption race open
(l2cap_chan_close without conn->lock). I'll send a follow-up patch
on top that addresses it.

Best,
Siwei

^ permalink raw reply

* Re: [GIT PULL] bluetooth 2026-05-14
From: Linus Torvalds @ 2026-05-20 19:32 UTC (permalink / raw)
  To: Luiz Augusto von Dentz
  Cc: Greg KH, August Wikerfors, Thorsten Leemhuis,
	stable@vger.kernel.org, Sasha Levin, linux-bluetooth, netdev,
	davem, kuba, Linux kernel regressions list
In-Reply-To: <CABBYNZKnrqHyASMOah795i9eteY7S5AfN3tCWssSRgqBXZwRMw@mail.gmail.com>

On Wed, 20 May 2026 at 08:53, Luiz Augusto von Dentz
<luiz.dentz@gmail.com> wrote:
>
> >
> > Just never rebase any public tree please.
>
> I guess the alternative is to do merges, right?

No. Back-merges are bad too, unless they have a really damn solid
reason for them, and some "keep up with other peoples work" is not
that.

The primary model should be that you care about your own work, and
make sure that that is as stable as possible. Do *NOT* try to chase
other people's work. Not with merges, not with rebases.

Then when your branch is all done and ready, you ask upstream (in your
case typically the networking tree) to pull it.

Some people at that point *jump* to the point where upstream merged from them.

Or another fairly common model is to have just started another branch
for future work. Keeping independent development branches for
different features is also a good thing to strive for, because it
makes it easier for different people to work on different branches
without messing with each other, but it also means that one feature
being delayed (due to unexpected problems or whatever) doesn't affect
other branches. And if done well, it also means that you wouldn't even
care about the whole "point where upstream merged", because your other
work simply isn't dependent on things like that.

So there are many ways to deal with them, but rebasing and merging are
typically the worst ones that should be avoided unless active problems
happen.

Sometimes you have to rebase because of a mistake. Sometimes you need
to do back-merges. But you should damn well have *reasons* for both
that aren't "that's just how we work".

Back-merges in particular need to not just be "merge upstream". They
need a commit message that explains *why* you're merging upstream (and
not merge some random point, the same way you shouldn't start
development at some random point of questionable stability).

Rebasing is "invisible" except for the mess it leaves with random
commit ids (and the problems it can cause for anybody who happened to
use an older version). So you can't explain it, but it should then be
explained to upstream why you ask them to pull recent changes that
clearly cannot have been tested in that form.

                Linus

^ permalink raw reply

* Re: [PATCH v7 RESEND 0/1] Bluetooth: L2CAP: Fix slab-use-after-free in l2cap_sock_cleanup_listen()
From: Luiz Augusto von Dentz @ 2026-05-20 19:40 UTC (permalink / raw)
  To: Siwei Zhang; +Cc: Marcel Holtmann, linux-bluetooth, Safa Karakuş
In-Reply-To: <ab53262f-329d-4c00-9575-8b3a8e96093a@app.fastmail.com>

Hi Siwei,

On Wed, May 20, 2026 at 2:57 PM Siwei Zhang <oss@fourdim.xyz> wrote:
>
> Hi Luiz,
>
> On Wed, May 20, 2026, at 2:26 PM, Luiz Augusto von Dentz wrote:
> > Hi Siwei,
> >
> > On Wed, May 20, 2026 at 12:39 PM Siwei Zhang <oss@fourdim.xyz> wrote:
> >>
> >> Hi Bluetooth maintainers,
> >>
> >> A public patch covering the same UAF in l2cap_sock_cleanup_listen() was posted to linux-bluetooth on April 28
> >> by Safa Karakuş. v4 is here:
> >>
> >> https://lore.kernel.org/linux-bluetooth/AS8P250MB079109F82C16BEDC4F9FE584EB372@AS8P250MB0791.EURP250.PROD.OUTLOOK.COM/
> >>
> >> I thanks for Safa's report and patch. I already reported the same issue privately to the maintainers in
> >> April 11th. The public patch breaks the embargo and I would like to resend my patch here.
> >>
> >> Safa's v4 closes the sk-lifetime hole (sock_hold inside bt_accept_dequeue) but does not take conn->lock around
> >> l2cap_chan_close, so the conn->chan_l list-corruption race in my report is still open after it.
> >
> > Are your changes on top of Safa's though? That seems a lot cleaner to be honest.
> >
>
> My patch is not on the top of Safa's. The diff looks quite different.
> I reported both the sk-lifetime UAF and the conn->chan_l list-corruption race
> privately to the maintainers on April 11th. And patch shortly on April 12th.

I'm afraid it doesn't matter if you reported first or not, Safa's fix
is much easier to understand. However, there still seems to be an
issue of calling l2cap_chan_del without holding the conn->lock.

> >> My patch closes both: it drops the parent sk_lock, acquires conn->lock → chan->lock in the established order
> >> to serialize the chan_l mutation, and re-takes the parent sk_lock before returning.
> >
> > I rather have each issue handled separately though.
> >
>
> I am happy to handle that separately.
>
> Could I get a Reported-by on Safa's patch since I reported the underlying issue before the public post?
>
> Reported-by: Siwei Zhang <oss@fourdim.xyz>

You can do it yourself, just respond to the thread. Bonus points if
you add a Tested-by if it actually fixes part of the problem reported.

> I'll send the conn->lock patch (drains accept queue to local list, drops parent sk_lock, acquires conn->lock -> chan_lock in
> established order) as another patch shortly.

Don't quite like that solution though, we should be dropping locks we
didn't acquire on the same scope, besides it seem that was doing a lot
of malabarism, perhaps we could just schedule l2cap_chan_timeout with
something like this:

diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
index 4ed745a9c2cf..413b1c63602a 100644
--- a/net/bluetooth/l2cap_sock.c
+++ b/net/bluetooth/l2cap_sock.c
@@ -1529,8 +1529,11 @@ static void l2cap_sock_cleanup_listen(struct
sock *parent)
                       state_to_string(chan->state));

                l2cap_chan_lock(chan);
-               __clear_chan_timer(chan);
-               l2cap_chan_close(chan, ECONNRESET);
+               /* Since we cannot call l2cap_chan_close() without
+                * chan->conn, schedule its timer to trigger the close and
+                * cleanup of this channel.
+                */
+               __set_chan_timer(chan, 0);
                /* l2cap_conn_del() may already have killed this socket
                 * (it sets SOCK_DEAD); skip the duplicate to avoid a
                 * double sock_put()/l2cap_chan_put().

Or perhaps it needs to be conditional to having chan->conn since that
indicates l2cap_chan_del has not run yet, making it safe to use
__set_chan_timer.

^ permalink raw reply related

* Re: [PATCH v4] Bluetooth: fix UAF in l2cap_sock_cleanup_listen() vs l2cap_conn_del()
From: patchwork-bot+bluetooth @ 2026-05-20 20:00 UTC (permalink / raw)
  To: =?utf-8?q?Safa_Karaku=C5=9F_=3Csafa=2Ekarakus=40secunnix=2Ecom=3E?=
  Cc: linux-bluetooth, luiz.dentz, marcel, stable, linux-kernel
In-Reply-To: <20260516181504.3076260-1-safa.karakus@secunnix.com>

Hello:

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

On Sat, 16 May 2026 21:15:04 +0300 you wrote:
> bt_accept_dequeue() unlinks a not-yet-accepted child from the parent
> accept queue and release_sock()s it before returning, so the returned
> sk has no caller reference and is unlocked.
> 
> l2cap_sock_cleanup_listen() walks these children on listening-socket
> close.  A concurrent HCI disconnect drives hci_rx_work ->
> l2cap_conn_del() which runs l2cap_chan_del() + l2cap_sock_kill() and
> frees the child sk and its l2cap_chan; cleanup_listen() then uses both:
> 
> [...]

Here is the summary with links:
  - [v4] Bluetooth: fix UAF in l2cap_sock_cleanup_listen() vs l2cap_conn_del()
    https://git.kernel.org/bluetooth/bluetooth-next/c/0b580042a1a5

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



^ permalink raw reply

* [PATCH] Bluetooth: L2CAP: use chan timer to close channels in cleanup_listen()
From: Siwei Zhang @ 2026-05-20 20:05 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: luiz.dentz, safa.karakus, stable, Siwei Zhang
In-Reply-To: <20260516181504.3076260-1-safa.karakus@secunnix.com>

l2cap_chan_close() removes the channel from conn->chan_l, which
must be done under conn->lock.  cleanup_listen() runs under the
parent sk_lock, so acquiring conn->lock would invert the
established conn->lock -> chan->lock -> sk_lock order.

Instead of calling l2cap_chan_close() directly, schedule
l2cap_chan_timeout with delay 0 to close the channel
asynchronously.  The timeout handler already acquires conn->lock
and chan->lock in the correct order.

The timer is only armed when chan->conn is still set: if it is
already NULL, l2cap_conn_del() has already processed this channel
(l2cap_chan_del + l2cap_sock_teardown_cb + l2cap_sock_close_cb),
so there is nothing left to do.  If l2cap_conn_del() races in
after the timer is armed, __clear_chan_timer() inside
l2cap_chan_del() cancels it; if the timer has already fired, the
handler returns harmlessly because chan->conn was cleared.

Fixes: 3df91ea20e74 ("Bluetooth: Revert to mutexes from RCU list")
Cc: stable@vger.kernel.org
Signed-off-by: Siwei Zhang <oss@fourdim.xyz>
---
 net/bluetooth/l2cap_sock.c | 16 +++++++++-------
 1 file changed, 9 insertions(+), 7 deletions(-)

diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
index 4ed745a9c2cf..025329636353 100644
--- a/net/bluetooth/l2cap_sock.c
+++ b/net/bluetooth/l2cap_sock.c
@@ -1512,6 +1512,10 @@ static void l2cap_sock_cleanup_listen(struct sock *parent)
 	 * pin it (hold_unless_zero() additionally skips a chan already past
 	 * its last reference).  We then drop the sk lock before taking
 	 * chan->lock, so sk and chan locks are never held together.
+	 *
+	 * Since we cannot call l2cap_chan_close() without conn->lock,
+	 * schedule l2cap_chan_timeout to close the channel; it already
+	 * acquires conn->lock -> chan->lock in the correct order.
 	 */
 	while ((sk = bt_accept_dequeue(parent, NULL))) {
 		struct l2cap_chan *chan;
@@ -1529,14 +1533,12 @@ static void l2cap_sock_cleanup_listen(struct sock *parent)
 		       state_to_string(chan->state));
 
 		l2cap_chan_lock(chan);
-		__clear_chan_timer(chan);
-		l2cap_chan_close(chan, ECONNRESET);
-		/* l2cap_conn_del() may already have killed this socket
-		 * (it sets SOCK_DEAD); skip the duplicate to avoid a
-		 * double sock_put()/l2cap_chan_put().
+		/* Since we cannot call l2cap_chan_close() without
+		 * conn->lock, schedule its timer to trigger the close
+		 * and cleanup of this channel.
 		 */
-		if (!sock_flag(sk, SOCK_DEAD))
-			l2cap_sock_kill(sk);
+		if (chan->conn)
+			__set_chan_timer(chan, 0);
 		l2cap_chan_unlock(chan);
 
 		l2cap_chan_put(chan);
-- 
2.54.0


^ permalink raw reply related

* Re: [PATCH v7 RESEND 0/1] Bluetooth: L2CAP: Fix slab-use-after-free in l2cap_sock_cleanup_listen()
From: Siwei Zhang @ 2026-05-20 20:08 UTC (permalink / raw)
  To: Luiz Augusto von Dentz
  Cc: Marcel Holtmann, linux-bluetooth, Safa Karakuş
In-Reply-To: <CABBYNZLWU4gzaLytLBD7V4fnFWyjXB4312tB2q00VRDTT6jb7g@mail.gmail.com>



On Wed, May 20, 2026, at 3:40 PM, Luiz Augusto von Dentz wrote:
> Hi Siwei,
>
> On Wed, May 20, 2026 at 2:57 PM Siwei Zhang <oss@fourdim.xyz> wrote:
>>
>> Hi Luiz,
>>
>> On Wed, May 20, 2026, at 2:26 PM, Luiz Augusto von Dentz wrote:
>> > Hi Siwei,
>> >
>> > On Wed, May 20, 2026 at 12:39 PM Siwei Zhang <oss@fourdim.xyz> wrote:
>> >>
>> >> Hi Bluetooth maintainers,
>> >>
>> >> A public patch covering the same UAF in l2cap_sock_cleanup_listen() was posted to linux-bluetooth on April 28
>> >> by Safa Karakuş. v4 is here:
>> >>
>> >> https://lore.kernel.org/linux-bluetooth/AS8P250MB079109F82C16BEDC4F9FE584EB372@AS8P250MB0791.EURP250.PROD.OUTLOOK.COM/
>> >>
>> >> I thanks for Safa's report and patch. I already reported the same issue privately to the maintainers in
>> >> April 11th. The public patch breaks the embargo and I would like to resend my patch here.
>> >>
>> >> Safa's v4 closes the sk-lifetime hole (sock_hold inside bt_accept_dequeue) but does not take conn->lock around
>> >> l2cap_chan_close, so the conn->chan_l list-corruption race in my report is still open after it.
>> >
>> > Are your changes on top of Safa's though? That seems a lot cleaner to be honest.
>> >
>>
>> My patch is not on the top of Safa's. The diff looks quite different.
>> I reported both the sk-lifetime UAF and the conn->chan_l list-corruption race
>> privately to the maintainers on April 11th. And patch shortly on April 12th.
>
> I'm afraid it doesn't matter if you reported first or not, Safa's fix
> is much easier to understand. However, there still seems to be an
> issue of calling l2cap_chan_del without holding the conn->lock.
>
>> >> My patch closes both: it drops the parent sk_lock, acquires conn->lock → chan->lock in the established order
>> >> to serialize the chan_l mutation, and re-takes the parent sk_lock before returning.
>> >
>> > I rather have each issue handled separately though.
>> >
>>
>> I am happy to handle that separately.
>>
>> Could I get a Reported-by on Safa's patch since I reported the underlying issue before the public post?
>>
>> Reported-by: Siwei Zhang <oss@fourdim.xyz>
>
> You can do it yourself, just respond to the thread. Bonus points if
> you add a Tested-by if it actually fixes part of the problem reported.
>
>> I'll send the conn->lock patch (drains accept queue to local list, drops parent sk_lock, acquires conn->lock -> chan_lock in
>> established order) as another patch shortly.
>
> Don't quite like that solution though, we should be dropping locks we
> didn't acquire on the same scope, besides it seem that was doing a lot
> of malabarism, perhaps we could just schedule l2cap_chan_timeout with
> something like this:
>
> diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
> index 4ed745a9c2cf..413b1c63602a 100644
> --- a/net/bluetooth/l2cap_sock.c
> +++ b/net/bluetooth/l2cap_sock.c
> @@ -1529,8 +1529,11 @@ static void l2cap_sock_cleanup_listen(struct
> sock *parent)
>                        state_to_string(chan->state));
>
>                 l2cap_chan_lock(chan);
> -               __clear_chan_timer(chan);
> -               l2cap_chan_close(chan, ECONNRESET);
> +               /* Since we cannot call l2cap_chan_close() without
> +                * chan->conn, schedule its timer to trigger the close and
> +                * cleanup of this channel.
> +                */
> +               __set_chan_timer(chan, 0);
>                 /* l2cap_conn_del() may already have killed this socket
>                  * (it sets SOCK_DEAD); skip the duplicate to avoid a
>                  * double sock_put()/l2cap_chan_put().
>
> Or perhaps it needs to be conditional to having chan->conn since that
> indicates l2cap_chan_del has not run yet, making it safe to use
> __set_chan_timer.

Thanks, that is much cleaner. 

PATCH sent to https://lore.kernel.org/linux-bluetooth/20260520200611.3033410-1-oss@fourdim.xyz/

Best,
Siwei

^ permalink raw reply

* Re: [PATCH] Bluetooth: L2CAP: use chan timer to close channels in cleanup_listen()
From: Luiz Augusto von Dentz @ 2026-05-20 20:29 UTC (permalink / raw)
  To: Siwei Zhang; +Cc: linux-bluetooth, safa.karakus, stable
In-Reply-To: <20260520200611.3033410-1-oss@fourdim.xyz>

Hi Siwei,

On Wed, May 20, 2026 at 4:06 PM Siwei Zhang <oss@fourdim.xyz> wrote:
>
> l2cap_chan_close() removes the channel from conn->chan_l, which
> must be done under conn->lock.  cleanup_listen() runs under the
> parent sk_lock, so acquiring conn->lock would invert the
> established conn->lock -> chan->lock -> sk_lock order.
>
> Instead of calling l2cap_chan_close() directly, schedule
> l2cap_chan_timeout with delay 0 to close the channel
> asynchronously.  The timeout handler already acquires conn->lock
> and chan->lock in the correct order.
>
> The timer is only armed when chan->conn is still set: if it is
> already NULL, l2cap_conn_del() has already processed this channel
> (l2cap_chan_del + l2cap_sock_teardown_cb + l2cap_sock_close_cb),
> so there is nothing left to do.  If l2cap_conn_del() races in
> after the timer is armed, __clear_chan_timer() inside
> l2cap_chan_del() cancels it; if the timer has already fired, the
> handler returns harmlessly because chan->conn was cleared.
>
> Fixes: 3df91ea20e74 ("Bluetooth: Revert to mutexes from RCU list")
> Cc: stable@vger.kernel.org
> Signed-off-by: Siwei Zhang <oss@fourdim.xyz>
> ---
>  net/bluetooth/l2cap_sock.c | 16 +++++++++-------
>  1 file changed, 9 insertions(+), 7 deletions(-)
>
> diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
> index 4ed745a9c2cf..025329636353 100644
> --- a/net/bluetooth/l2cap_sock.c
> +++ b/net/bluetooth/l2cap_sock.c
> @@ -1512,6 +1512,10 @@ static void l2cap_sock_cleanup_listen(struct sock *parent)
>          * pin it (hold_unless_zero() additionally skips a chan already past
>          * its last reference).  We then drop the sk lock before taking
>          * chan->lock, so sk and chan locks are never held together.
> +        *
> +        * Since we cannot call l2cap_chan_close() without conn->lock,
> +        * schedule l2cap_chan_timeout to close the channel; it already
> +        * acquires conn->lock -> chan->lock in the correct order.
>          */
>         while ((sk = bt_accept_dequeue(parent, NULL))) {
>                 struct l2cap_chan *chan;
> @@ -1529,14 +1533,12 @@ static void l2cap_sock_cleanup_listen(struct sock *parent)
>                        state_to_string(chan->state));
>
>                 l2cap_chan_lock(chan);
> -               __clear_chan_timer(chan);
> -               l2cap_chan_close(chan, ECONNRESET);
> -               /* l2cap_conn_del() may already have killed this socket
> -                * (it sets SOCK_DEAD); skip the duplicate to avoid a
> -                * double sock_put()/l2cap_chan_put().
> +               /* Since we cannot call l2cap_chan_close() without
> +                * conn->lock, schedule its timer to trigger the close
> +                * and cleanup of this channel.
>                  */
> -               if (!sock_flag(sk, SOCK_DEAD))
> -                       l2cap_sock_kill(sk);
> +               if (chan->conn)
> +                       __set_chan_timer(chan, 0);

Great that seems a lot easier to understand than the previous changes.
I just don't quite follow why you are removing SOCK_DEAD handling with
this?

>                 l2cap_chan_unlock(chan);
>
>                 l2cap_chan_put(chan);
> --
> 2.54.0
>


-- 
Luiz Augusto von Dentz

^ permalink raw reply

* [GIT PULL] bluetooth 2026-05-20
From: Luiz Augusto von Dentz @ 2026-05-20 20:49 UTC (permalink / raw)
  To: davem, kuba; +Cc: linux-bluetooth, netdev

The following changes since commit 375ba7484132662a4a8c7547d088fb6275c00282:

  Bluetooth: hci_qca: Convert timeout from jiffies to ms (2026-05-14 09:58:08 -0400)

are available in the Git repository at:

  git://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth.git tags/for-net-2026-05-20

for you to fetch changes up to ab1513597c6cf17cd1ad2a21e3b045421b48e022:

  Bluetooth: fix UAF in l2cap_sock_cleanup_listen() vs l2cap_conn_del() (2026-05-20 16:35:47 -0400)

----------------------------------------------------------------
bluetooth pull request for net:

 - hci_sync: Fix not setting mask for HCI_EVT_LE_ALL_REMOTE_FEATURES_COMPLETE
 - L2CAP: fix UAF in l2cap_sock_cleanup_listen() vs l2cap_conn_del()
 - ISO: drop ISO_END frames received without prior ISO_START
 - MGMT: validate Add Extended Advertising Data length
 - bnep: Fix UAF read of dev->name
 - btmtk: fix urb->setup_packet leak in error paths
 - btintel_pcie: Fix incorrect MAC access programming
 - hci_uart: fix UAFs and race conditions in close and init paths

----------------------------------------------------------------
David Carlier (1):
      Bluetooth: ISO: drop ISO_END frames received without prior ISO_START

Jann Horn (1):
      Bluetooth: bnep: Fix UAF read of dev->name

Jiajia Liu (1):
      Bluetooth: btmtk: fix urb->setup_packet leak in error paths

Kiran K (1):
      Bluetooth: btintel_pcie: Fix incorrect MAC access programming

Luiz Augusto von Dentz (1):
      Bluetooth: hci_sync: Fix not setting mask for HCI_EVT_LE_ALL_REMOTE_FEATURES_COMPLETE

Michael Bommarito (1):
      Bluetooth: MGMT: validate Add Extended Advertising Data length

Mingyu Wang (1):
      Bluetooth: hci_uart: fix UAFs and race conditions in close and init paths

Safa Karakuş (1):
      Bluetooth: fix UAF in l2cap_sock_cleanup_listen() vs l2cap_conn_del()

 drivers/bluetooth/btintel_pcie.c | 20 +++++-----------
 drivers/bluetooth/btintel_pcie.h |  3 ---
 drivers/bluetooth/btmtk.c        |  2 ++
 drivers/bluetooth/hci_ldisc.c    | 48 ++++++++++++++++++++++++++++++-------
 net/bluetooth/af_bluetooth.c     | 10 ++++++++
 net/bluetooth/bnep/core.c        |  2 +-
 net/bluetooth/hci_sync.c         |  6 ++---
 net/bluetooth/iso.c              | 14 ++++++++++-
 net/bluetooth/l2cap_sock.c       | 51 ++++++++++++++++++++++++++++++++++------
 net/bluetooth/mgmt.c             |  6 +++++
 net/bluetooth/rfcomm/sock.c      |  9 ++++++-
 net/bluetooth/sco.c              |  9 ++++++-
 12 files changed, 141 insertions(+), 39 deletions(-)

^ permalink raw reply

* RE: Bluetooth: L2CAP: use chan timer to close channels in cleanup_listen()
From: bluez.test.bot @ 2026-05-20 21:28 UTC (permalink / raw)
  To: linux-bluetooth, oss
In-Reply-To: <20260520200611.3033410-1-oss@fourdim.xyz>

[-- Attachment #1: Type: text/plain, Size: 555 bytes --]

This is an automated email and please do not reply to this email.

Dear Submitter,

Thank you for submitting the patches to the linux bluetooth mailing list.
While preparing the CI tests, the patches you submitted couldn't be applied to the current HEAD of the repository.

----- Output -----

error: patch failed: net/bluetooth/l2cap_sock.c:1512
error: net/bluetooth/l2cap_sock.c: patch does not apply
hint: Use 'git am --show-current-patch' to see the failed patch

Please resolve the issue and submit the patches again.


---
Regards,
Linux Bluetooth


^ permalink raw reply

* [PATCH v2] Bluetooth: HIDP: fix missing length checks in hidp_input_report()
From: Muhammad Bilal @ 2026-05-20 21:41 UTC (permalink / raw)
  To: linux-bluetooth
  Cc: linux-kernel, marcel, luiz.dentz, johan.hedberg, Muhammad Bilal,
	stable
In-Reply-To: <20260517234805.116570-1-meatuni001@gmail.com>

hidp_input_report() reads keyboard and mouse payload data from an skb
without first verifying that skb->len contains enough data.

hidp_recv_intr_frame() pulls the 1-byte HIDP header before dispatching
to hidp_input_report(). If a paired device sends a truncated packet,
the handler reads beyond the valid skb data, resulting in an
out-of-bounds read of skb data. The OOB bytes may be interpreted as
phantom key presses or spurious mouse movement.

Add a check that skb->len is non-zero before the type switch, and
per-report-type minimum length checks before accessing the payload.

Cc: stable@vger.kernel.org
Signed-off-by: Muhammad Bilal <meatuni001@gmail.com>
---
 net/bluetooth/hidp/core.c | 19 ++++++++++++++++---
 1 file changed, 16 insertions(+), 3 deletions(-)

diff --git a/net/bluetooth/hidp/core.c b/net/bluetooth/hidp/core.c
index 976f91eeb..03838a6ff 100644
--- a/net/bluetooth/hidp/core.c
+++ b/net/bluetooth/hidp/core.c
@@ -179,12 +179,22 @@ static void hidp_input_report(struct hidp_session *session, struct sk_buff *skb)
 {
 	struct input_dev *dev = session->input;
 	unsigned char *keys = session->keys;
-	unsigned char *udata = skb->data + 1;
-	signed char *sdata = skb->data + 1;
-	int i, size = skb->len - 1;
+	unsigned char *udata;
+	signed char *sdata;
+	int i, size;
+
+	if (!skb->len)
+		return;
+
+	udata = skb->data + 1;
+	sdata = skb->data + 1;
+	size = skb->len - 1;
 
 	switch (skb->data[0]) {
 	case 0x01:	/* Keyboard report */
+		if (size < 8)
+			break;
+
 		for (i = 0; i < 8; i++)
 			input_report_key(dev, hidp_keycode[i + 224], (udata[0] >> i) & 1);
 
@@ -213,6 +223,9 @@ static void hidp_input_report(struct hidp_session *session, struct sk_buff *skb)
 		break;
 
 	case 0x02:	/* Mouse report */
+		if (size < 3)
+			break;
+
 		input_report_key(dev, BTN_LEFT,   sdata[0] & 0x01);
 		input_report_key(dev, BTN_RIGHT,  sdata[0] & 0x02);
 		input_report_key(dev, BTN_MIDDLE, sdata[0] & 0x04);
-- 
2.54.0


^ permalink raw reply related

* Re: [PATCH v2] Bluetooth: HIDP: fix missing length checks in hidp_input_report()
From: Luiz Augusto von Dentz @ 2026-05-20 22:03 UTC (permalink / raw)
  To: Muhammad Bilal
  Cc: linux-bluetooth, linux-kernel, marcel, johan.hedberg, stable
In-Reply-To: <20260520214133.27746-1-meatuni001@gmail.com>

Hi Muhammad,

On Wed, May 20, 2026 at 5:41 PM Muhammad Bilal <meatuni001@gmail.com> wrote:
>
> hidp_input_report() reads keyboard and mouse payload data from an skb
> without first verifying that skb->len contains enough data.
>
> hidp_recv_intr_frame() pulls the 1-byte HIDP header before dispatching
> to hidp_input_report(). If a paired device sends a truncated packet,
> the handler reads beyond the valid skb data, resulting in an
> out-of-bounds read of skb data. The OOB bytes may be interpreted as
> phantom key presses or spurious mouse movement.
>
> Add a check that skb->len is non-zero before the type switch, and
> per-report-type minimum length checks before accessing the payload.
>
> Cc: stable@vger.kernel.org
> Signed-off-by: Muhammad Bilal <meatuni001@gmail.com>
> ---
>  net/bluetooth/hidp/core.c | 19 ++++++++++++++++---
>  1 file changed, 16 insertions(+), 3 deletions(-)
>
> diff --git a/net/bluetooth/hidp/core.c b/net/bluetooth/hidp/core.c
> index 976f91eeb..03838a6ff 100644
> --- a/net/bluetooth/hidp/core.c
> +++ b/net/bluetooth/hidp/core.c
> @@ -179,12 +179,22 @@ static void hidp_input_report(struct hidp_session *session, struct sk_buff *skb)
>  {
>         struct input_dev *dev = session->input;
>         unsigned char *keys = session->keys;
> -       unsigned char *udata = skb->data + 1;
> -       signed char *sdata = skb->data + 1;
> -       int i, size = skb->len - 1;
> +       unsigned char *udata;
> +       signed char *sdata;
> +       int i, size;
> +
> +       if (!skb->len)
> +               return;
> +
> +       udata = skb->data + 1;
> +       sdata = skb->data + 1;
> +       size = skb->len - 1;

If you use skb_pull_data, you won't need to use pointer arithmetic, or
store the actual size.

>
>         switch (skb->data[0]) {
>         case 0x01:      /* Keyboard report */
> +               if (size < 8)
> +                       break;
> +
>                 for (i = 0; i < 8; i++)
>                         input_report_key(dev, hidp_keycode[i + 224], (udata[0] >> i) & 1);
>
> @@ -213,6 +223,9 @@ static void hidp_input_report(struct hidp_session *session, struct sk_buff *skb)
>                 break;
>
>         case 0x02:      /* Mouse report */
> +               if (size < 3)
> +                       break;
> +
>                 input_report_key(dev, BTN_LEFT,   sdata[0] & 0x01);
>                 input_report_key(dev, BTN_RIGHT,  sdata[0] & 0x02);
>                 input_report_key(dev, BTN_MIDDLE, sdata[0] & 0x04);
> --
> 2.54.0
>


-- 
Luiz Augusto von Dentz

^ permalink raw reply


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