]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
Bluetooth: L2CAP: reject BR/EDR signaling packets over MTUsig
authorMichael Bommarito <michael.bommarito@gmail.com>
Thu, 21 May 2026 14:45:17 +0000 (10:45 -0400)
committerLuiz Augusto von Dentz <luiz.von.dentz@intel.com>
Wed, 3 Jun 2026 15:21:24 +0000 (11:21 -0400)
net/bluetooth/l2cap_core.c:l2cap_sig_channel() accepts BR/EDR
signaling packets up to the channel MTU and dispatches each command
without enforcing the signaling MTU (MTUsig). A Bluetooth BR/EDR peer
within radio range can send a fixed-channel CID 0x0001 packet that is
larger than MTUsig and contains many L2CAP_ECHO_REQ commands before
pairing. In a real-radio stock-kernel run, one 681-byte signaling
packet containing 168 zero-length ECHO_REQ commands made the target
transmit 168 ECHO_RSP frames over about 220 ms.

Impact: a Bluetooth BR/EDR peer within radio range, before pairing, can
force 168 ECHO_RSP frames from one 681-byte fixed-channel signaling
packet containing packed ECHO_REQ commands.

Define Linux's BR/EDR signaling MTU as the spec minimum of 48 bytes and
reject any larger signaling packet with one L2CAP_COMMAND_REJECT_RSP
carrying L2CAP_REJ_MTU_EXCEEDED before any command is dispatched.

The Bluetooth Core spec wording for MTUExceeded says the reject
identifier shall match the first request command in the packet, and
that packets containing only responses shall be silently discarded.
Linux intentionally deviates from that prescription: silently
discarding desynchronizes the peer because the remote stack never
learns its responses were dropped, and locating the first request
command requires walking command headers past MTUsig, i.e. processing
bytes from a packet we have already decided is too large to process.
We therefore always emit one reject and use the identifier from the
first command header, a single fixed-offset byte read.

The unrestricted BR/EDR signaling parser and ECHO_REQ response path both
trace to the initial git import; no later introducing commit is
available for a Fixes tag.

Cc: stable@vger.kernel.org
Suggested-by: Luiz Augusto von Dentz <luiz.dentz@gmail.com>
Link: https://lore.kernel.org/r/20260518002800.1361430-1-michael.bommarito@gmail.com
Link: https://lore.kernel.org/r/20260520135034.1060859-1-michael.bommarito@gmail.com
Link: https://lore.kernel.org/r/20260521000555.3712030-1-michael.bommarito@gmail.com
Assisted-by: Claude:claude-opus-4-7
Assisted-by: Codex:gpt-5-5-xhigh
Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
include/net/bluetooth/l2cap.h
net/bluetooth/l2cap_core.c

index 5172afee5494332fdeb47c158fd036a2e5c6dda7..e0a1f2293679af6316500fbd2bc66ebac35fb3cd 100644 (file)
@@ -33,6 +33,7 @@
 /* L2CAP defaults */
 #define L2CAP_DEFAULT_MTU              672
 #define L2CAP_DEFAULT_MIN_MTU          48
+#define L2CAP_SIG_MTU                  48      /* BR/EDR signaling MTU */
 #define L2CAP_DEFAULT_FLUSH_TO         0xFFFF
 #define L2CAP_EFS_DEFAULT_FLUSH_TO     0xFFFFFFFF
 #define L2CAP_DEFAULT_TX_WINDOW                63
index 45b175399e8dbbfd5c2c3adee79628e6b4ab5121..c4ccfbda9d7890c68a0fed27a762e0a4ed17bddd 100644 (file)
@@ -5643,6 +5643,15 @@ static inline void l2cap_sig_send_rej(struct l2cap_conn *conn, u16 ident)
        l2cap_send_cmd(conn, ident, L2CAP_COMMAND_REJ, sizeof(rej), &rej);
 }
 
+static inline void l2cap_sig_send_mtu_rej(struct l2cap_conn *conn, u8 ident)
+{
+       struct l2cap_cmd_rej_mtu rej;
+
+       rej.reason = cpu_to_le16(L2CAP_REJ_MTU_EXCEEDED);
+       rej.max_mtu = cpu_to_le16(L2CAP_SIG_MTU);
+       l2cap_send_cmd(conn, ident, L2CAP_COMMAND_REJ, sizeof(rej), &rej);
+}
+
 static inline void l2cap_sig_channel(struct l2cap_conn *conn,
                                     struct sk_buff *skb)
 {
@@ -5655,6 +5664,43 @@ static inline void l2cap_sig_channel(struct l2cap_conn *conn,
        if (hcon->type != ACL_LINK)
                goto drop;
 
+       /*
+        * Bluetooth Core v5.4, Vol 3, Part A, Section 4: the BR/EDR
+        * signaling channel has a fixed signaling MTU (MTUsig) whose
+        * minimum and default is 48 octets.  Section 4.1 says that on
+        * an MTUExceeded command reject the identifier "shall match
+        * the first request command in the L2CAP packet" and that
+        * packets containing only response commands "shall be
+        * silently discarded".
+        *
+        * Linux intentionally deviates from that prescription:
+        *
+        *   1. Silently discarding desynchronizes the peer.  The
+        *      remote stack never learns its responses were dropped,
+        *      so any state machine waiting on a paired response
+        *      stalls until its own timer fires.
+        *
+        *   2. Locating "the first request command" requires walking
+        *      command headers past MTUsig, i.e. processing bytes
+        *      from a packet we have already decided is too large to
+        *      process.
+        *
+        * Reject every over-MTUsig signaling packet with one
+        * L2CAP_REJ_MTU_EXCEEDED command reject.  The reject's
+        * reason field is what tells the peer that the whole packet
+        * was discarded; the identifier value is informational, so
+        * we use the identifier from the first command header, a
+        * single fixed-offset byte read.
+        */
+       if (skb->len > L2CAP_SIG_MTU) {
+               u8 ident = skb->data[1];
+
+               BT_DBG("signaling packet exceeds MTU: %u > %u",
+                      skb->len, L2CAP_SIG_MTU);
+               l2cap_sig_send_mtu_rej(conn, ident);
+               goto drop;
+       }
+
        while (skb->len >= L2CAP_CMD_HDR_SIZE) {
                u16 len;