From nobody Sun May 24 22:33:46 2026 Received: from mail-qk1-f174.google.com (mail-qk1-f174.google.com [209.85.222.174]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9FAE9126BF1 for ; Thu, 21 May 2026 00:06:10 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.222.174 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779321974; cv=none; b=MImRfHoFtk3ZHeqOlh/2owlZ9rNP75zgPAiwoQzQFJywGNm+hddbqKsodNCmGtgareeATnnXxZf4S3qjAeIyikbkdLZH/CY51pV23euSExFCxGJLlaFfS+MkrSnKaVLrCIlESEIZmCch2LVVlJjm87iQtFcvEUIVy7dwFVDKdXY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779321974; c=relaxed/simple; bh=zahVRzkLToA6oMFieq+LNgXnW2Tf/2BUPcvcXVza/7Q=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=EOeHVdVUp1AGyfMtrJh9x3tkGASA3xM9SDhY8DzkJYP+RksakBIANJW7tRFie7QlUUFN/pXIS5PYKnF/vpySoIndg7yGRkPKhgtbuhxf+3pmM2BAE5gImV3Ie981D2NdQB5q6UtSeOd8NPFWaovWcNrmFpUPWP+C4+qr2B3QF+M= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=ZdKfB16T; arc=none smtp.client-ip=209.85.222.174 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="ZdKfB16T" Received: by mail-qk1-f174.google.com with SMTP id af79cd13be357-90d13fa59e8so554733685a.2 for ; Wed, 20 May 2026 17:06:10 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1779321970; x=1779926770; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=2ZNvk1BovRx97OnCimNnt74dFamxA5DuthwM7CtyImQ=; b=ZdKfB16TSYpDIZbZ0DLkuPoClmrOk0o8zUySVIJcBkr6zLq/QhJUEBU9xrmh1/TppG B4iIpo5WdElfingawitMeD8dNulFNSCEa2Ez8QSD5f9eMkEVeyI7a6C3vKAHZJahpMhm V6amTfRI74lQaQ62d0TqoIMlRAM2X82na8FfeRWcQPLCsV3WlRkWcifhiy9RJ76EOoBG NGUpEoOO8jhqDmimROmmsYX5ZvqZ3mWnhHH1RiG5ZKjqHTO0EItwpo63UavzGd6/o5or 0dXXbvdl7XAddPdUwFl5v2VM2K5K2dplVs7bW4s/WJRWgEa4mETWc8Vf+OLG7HukpdIS YZXA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779321970; x=1779926770; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=2ZNvk1BovRx97OnCimNnt74dFamxA5DuthwM7CtyImQ=; b=JZ08AaFBtnmWsm/hxLGEdY9r76DVPmqIyZYnT0oon5C7mLCmvOM+22R5szl9KVcXFV UADYH/EOSNTGyNiyZudYIXnwyPQEOy7mdjILtfNN1Kf9Y0ZXa3vOzWW23yHj8dy+s1zm p3FEeNZNt32dgOSanGpoM6ttzip6gAoHWF5xz0wb9gZQBd+ekTfjsQMg0FRCSFEIMhsj TmD3gjc/TqOTj+KX7aiLXA15VGSiRcHOsdrR6URF0OiZg0f3ZkBOx6qibEwK6ytOJb37 Z9JHQqsjBjGr2jCOUc38qlDN2Q1NZFJmLEzs18B5l7VkDAXE3bM2OT3Y0S/wjRSmiCyR yRhA== X-Forwarded-Encrypted: i=1; AFNElJ//YwbyCEwnnX/sg9CxLcvBa8gHiuqr/nZ7DClMa4Z+HM7oRie+P7Ht9klCQPm10oynyCFeNHba4BLnFpo=@vger.kernel.org X-Gm-Message-State: AOJu0YwwPQlxR4akaS20hddFasnrUPs+mz27TxeCl95warQ/0NRHe7rz WBPHOKqJAYtctv/H0n0aWMQY8j4sW70v/M/IeQsVPNEalB07AtBlxwOo X-Gm-Gg: Acq92OEoWC1QflxAyznXB7GP5c5UuwoGaNRVuiL9TdUUfV/S32cLawl5O4O9yNc5WGe 8kGdL0DW+lPS9xI+YJ2WPI3Nbqgux3SPZ2rw8KbXRmGliI85V2SYCz4FhK+XDK928Q4XhYL7qX8 eT2K/+jPtYjc80kg4vaYqlBIJ8GnU87YRVGYbnFc96G0M1SOBqIYG6WqMx7vUJYByXtbuchGxdX 69/EKHGub8iESkjDx596gtwRgp97eLR87SBaI9IaJ0Vo8/Kadup/oZGUTTXoL035BmSFpPrBTdg SgQCjzgeuD+5hHjb+GFVzK+XJCf0jgMOGZl1Jg3AwJdzpFTcpWwbQYrqCtBTKn06nb2RD+vtTFg Fk0QnJYSGDR0FMndCpOpRjd1Nrrmy6L9V1vnlDtQZTFZ6LO716TwBVmC1JrqHe+eZqi6/0N+o5t Cfa4b5QlzqKOM4qkfa+DVxpAfyZpIa9U2mQ1EhF4xduHuebf1IbdkFaSQjhth8Dje1Q7iITSTx+ dmkuvUJT92BkCirHFpI6z4eMepo8og= X-Received: by 2002:a05:620a:19a0:b0:912:5d2a:4baf with SMTP id af79cd13be357-914a2e507e2mr67313885a.61.1779321969362; Wed, 20 May 2026 17:06:09 -0700 (PDT) Received: from server0 (c-68-48-65-54.hsd1.mi.comcast.net. [68.48.65.54]) by smtp.gmail.com with ESMTPSA id af79cd13be357-910bcf353b9sm2360405085a.35.2026.05.20.17.06.08 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 20 May 2026 17:06:08 -0700 (PDT) From: Michael Bommarito To: Marcel Holtmann , Luiz Augusto von Dentz Cc: "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , linux-bluetooth@vger.kernel.org, netdev@vger.kernel.org, linux-kernel@vger.kernel.org, stable@vger.kernel.org Subject: [PATCH v3] Bluetooth: L2CAP: reject BR/EDR signaling packets over MTUsig Date: Wed, 20 May 2026 20:05:55 -0400 Message-ID: <20260521000555.3712030-1-michael.bommarito@gmail.com> X-Mailer: git-send-email 2.53.0 In-Reply-To: <20260520135034.1060859-1-michael.bommarito@gmail.com> References: <20260520135034.1060859-1-michael.bommarito@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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), falling back to zero when the packet is too short to carry a header at all. 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 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 Assisted-by: Claude:claude-opus-4-7 Assisted-by: Codex:gpt-5-5-xhigh Signed-off-by: Michael Bommarito --- I reproduced the stock behavior with a real-radio BR/EDR ACL link and a harness that sends a single fixed-channel signaling packet containing packed zero-length ECHO_REQ commands, and confirmed on a patched kernel that the same packet now produces one L2CAP_REJ_MTU_EXCEEDED command reject and zero ECHO_RSP frames. The patched code builds for net/bluetooth/l2cap_core.o on x86_64 defconfig with W=3D1. There are no in-tree Bluetooth selftests that reference l2cap_sig_channel(), L2CAP_SIG_MTU, or L2CAP_ECHO_REQ. Changes in v3: - Drop l2cap_sig_cmd_is_req() and l2cap_sig_first_req_ident(); the reject is now unconditional and uses only the first command header's identifier byte at a fixed offset. Per Luiz, the spec's "match the first request command identifier" rule would require parsing past MTUsig, and the spec's "silently discard if only responses" rule desynchronizes the peer. - Replace the v2 walk with a verbose comment quoting the relevant Bluetooth Core section and documenting why Linux deviates. Changes in v2: - Replace the per-PDU echo-count cap with the MTUsig direction from review. - Reject the whole over-MTUsig signaling packet with one L2CAP_REJ_MTU_EXCEEDED command reject. - Add L2CAP_SIG_MTU and drop over-MTUsig packets when no valid request command identifier is found. v1: https://lore.kernel.org/r/20260518002800.1361430-1-michael.bommarito@gm= ail.com v2: https://lore.kernel.org/r/20260520135034.1060859-1-michael.bommarito@gm= ail.com --- include/net/bluetooth/l2cap.h | 1 + net/bluetooth/l2cap_core.c | 47 +++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h index 5172afee54943..e0a1f2293679a 100644 --- a/include/net/bluetooth/l2cap.h +++ b/include/net/bluetooth/l2cap.h @@ -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 diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 7701528f11677..0b1e062057695 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -5618,6 +5618,15 @@ static inline void l2cap_sig_send_rej(struct l2cap_c= onn *conn, u16 ident) l2cap_send_cmd(conn, ident, L2CAP_COMMAND_REJ, sizeof(rej), &rej); } =20 +static inline void l2cap_sig_send_mtu_rej(struct l2cap_conn *conn, u8 iden= t) +{ + struct l2cap_cmd_rej_mtu rej; + + rej.reason =3D cpu_to_le16(L2CAP_REJ_MTU_EXCEEDED); + rej.max_mtu =3D 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) { @@ -5630,6 +5639,44 @@ static inline void l2cap_sig_channel(struct l2cap_co= nn *conn, if (hcon->type !=3D ACL_LINK) goto drop; =20 + /* + * 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) or zero when the packet is + * too short to carry even one header. + */ + if (skb->len > L2CAP_SIG_MTU) { + u8 ident =3D (skb->len >=3D L2CAP_CMD_HDR_SIZE) ? + skb->data[1] : 0; + + BT_DBG("signaling packet exceeds MTU"); + l2cap_sig_send_mtu_rej(conn, ident); + goto drop; + } + while (skb->len >=3D L2CAP_CMD_HDR_SIZE) { u16 len; =20 --=20 2.53.0