Linux bluetooth development
 help / color / mirror / Atom feed
* [PATCH] Bluetooth: L2CAP: don't reserve an ident for L2CAP_LE_CREDITS
@ 2026-11-15 11:53 Nguyen Ngoc Thang
  2026-11-15 18:08 ` bluez.test.bot
  0 siblings, 1 reply; 2+ messages in thread
From: Nguyen Ngoc Thang @ 2026-11-15 11:53 UTC (permalink / raw)
  To: Marcel Holtmann, Luiz Augusto von Dentz
  Cc: linux-bluetooth, linux-kernel, Stig Hornang,
	syzbot+9dc8780713b11155dd74

l2cap_chan_le_send_credits() allocates an ident from conn->tx_ida, sends
L2CAP_LE_CREDITS and then frees the ident unconditionally, since the
command has no response.

But the ident is visible in tx_ida while the command is in flight, and
the remote may answer it with L2CAP_COMMAND_REJ anyway. The RX path then
releases it in l2cap_put_ident() under conn->lock, while the credits path
runs from recvmsg() with only the socket lock held. Whichever ida_free()
comes second hits:

  ida_free called for id=3 which is not allocated.
  WARNING: lib/idr.c:594 at ida_free+0x24c/0x2d4 lib/idr.c:594
  Call trace:
   ida_free+0x24c/0x2d4 lib/idr.c:594
   l2cap_chan_le_send_credits+0x17c/0x29c net/bluetooth/l2cap_core.c:7113
   l2cap_chan_rx_avail+0xb4/0x134 net/bluetooth/l2cap_core.c:7126
   l2cap_publish_rx_avail+0x110/0x168 net/bluetooth/l2cap_sock.c
   l2cap_sock_recvmsg+0x280/0x7b8 net/bluetooth/l2cap_sock.c:1317

Worse, if the ident is reallocated for a real request in between, the
credits path frees that request's ident instead.

tx_ida only exists to track requests awaiting a response, so a command
without one has no business in it. Give L2CAP_LE_CREDITS a plain cyclic
ident instead, which never needs releasing. The ident is still never 0
and never exhausts the range, which is what commit 6e1930ece855
("Bluetooth: L2CAP: fix tx ident leak for commands without a response")
set out to fix.

Fixes: 6e1930ece855 ("Bluetooth: L2CAP: fix tx ident leak for commands without a response")
Reported-by: syzbot+9dc8780713b11155dd74@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=9dc8780713b11155dd74
Signed-off-by: Nguyen Ngoc Thang <ngocthang2710.1999@gmail.com>
---
Tested in QEMU (x86_64, KASAN + lockdep) with the syzbot C reproducer,
15 runs per kernel:
  base (origin/master 23c62304dde2): 31x "ida_free called for id=N ..."
  patched:                           0 warnings, 1200 LE credits sent,
                                     none with ident 0 (ident wraps fine)

 net/bluetooth/l2cap_core.c | 44 +++++++++++++++++++++-----------------
 1 file changed, 24 insertions(+), 20 deletions(-)

diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index 7dcb0586a10b..0b861ced5703 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -1028,21 +1028,36 @@ int l2cap_chan_check_security(struct l2cap_chan *chan, bool initiator)
 				 initiator);
 }
 
-static int l2cap_get_ident(struct l2cap_conn *conn)
+static u8 l2cap_ident_max(struct l2cap_conn *conn)
 {
-	u8 max;
-	int ident;
-
 	/* LE link does not support tools like l2ping so use the full range */
 	if (conn->hcon->type == LE_LINK)
-		max = 255;
+		return 255;
+
 	/* Get next available identificator.
 	 *    1 - 128 are used by kernel.
 	 *  129 - 199 are reserved.
 	 *  200 - 254 are used by utilities like l2ping, etc.
 	 */
-	else
-		max = 128;
+	return 128;
+}
+
+/* Ident for a command that has no response: it is not reserved in tx_ida,
+ * so nothing has to release it and it can't be released twice.
+ */
+static u8 l2cap_next_ident(struct l2cap_conn *conn)
+{
+	u8 ident = READ_ONCE(conn->tx_ident) % l2cap_ident_max(conn) + 1;
+
+	WRITE_ONCE(conn->tx_ident, ident);
+
+	return ident;
+}
+
+static int l2cap_get_ident(struct l2cap_conn *conn)
+{
+	u8 max = l2cap_ident_max(conn);
+	int ident;
 
 	/* Allocate ident using min as last used + 1 (cyclic) */
 	ident = ida_alloc_range(&conn->tx_ida, READ_ONCE(conn->tx_ident) + 1,
@@ -7081,7 +7096,6 @@ static void l2cap_chan_le_send_credits(struct l2cap_chan *chan)
 	struct l2cap_conn *conn = chan->conn;
 	struct l2cap_le_credits pkt;
 	u16 return_credits = l2cap_le_rx_credits(chan);
-	int ident;
 
 	if (chan->mode != L2CAP_MODE_LE_FLOWCTL &&
 	    chan->mode != L2CAP_MODE_EXT_FLOWCTL)
@@ -7099,18 +7113,8 @@ static void l2cap_chan_le_send_credits(struct l2cap_chan *chan)
 	pkt.cid     = cpu_to_le16(chan->scid);
 	pkt.credits = cpu_to_le16(return_credits);
 
-	ident = l2cap_get_ident(conn);
-
-	l2cap_send_cmd(conn, ident, L2CAP_LE_CREDITS, sizeof(pkt), &pkt);
-
-	/* L2CAP_LE_CREDITS has no response so the ident is never released by
-	 * l2cap_put_ident() - release it right away, otherwise the tx_ida
-	 * range is exhausted after 254 packets and from then on credits are
-	 * sent with the invalid ident 0, which some remote stacks ignore,
-	 * stalling the channel.
-	 */
-	if (ident > 0)
-		ida_free(&conn->tx_ida, ident);
+	l2cap_send_cmd(conn, l2cap_next_ident(conn), L2CAP_LE_CREDITS,
+		       sizeof(pkt), &pkt);
 }
 
 void l2cap_chan_rx_avail(struct l2cap_chan *chan, ssize_t rx_avail)
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-11-15 18:08 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-15 11:53 [PATCH] Bluetooth: L2CAP: don't reserve an ident for L2CAP_LE_CREDITS Nguyen Ngoc Thang
2026-11-15 18:08 ` bluez.test.bot

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