Denial of Service

HIGH
torvalds/linux
Commit: dd2147335444
Affected: < v7.0-rc6 (pre-patch)
2026-06-05 15:29 UTC

Description

The commit fixes a Denial of Service vector in the BR/EDR signaling path of Linux Bluetooth (L2CAP). Prior to this patch, the BR/EDR signaling channel allowed packets larger than the BR/EDR signaling MTU (MTUsig, defined as 48 octets) to be accepted and parsed. An attacker in radio range could send a single oversized BR/EDR signaling packet (e.g., ~681 bytes) on the fixed-channel CID 0x0001 containing many L2CAP_ECHO_REQ commands. If processed, this could trigger a flood of L2CAP_ECHO_RSP frames (e.g., 168 responses) in a short time, causing CPU/L2CAP stack churn and a DoS. The patch enforces MTU for BR/EDR signaling by rejecting oversized signaling packets with a L2CAP_REJ MTU_EXCEEDED, before any command processing, using the first command header's identifier for the rejection. This fixes the DoS vector by eliminating the processing of oversized packets and preventing echo floods. The patch also defines L2CAP_SIG_MTU as 48 bytes.

Proof of Concept

PoC steps: 1. Set up a test lab with a victim Linux host and a BR/EDR-capable attacker device within Bluetooth range. Ensure the victim's Bluetooth is enabled but not yet paired. 2. Use a kernel with the vulnerable behavior (pre-patch) to reproduce the DoS, or test with the patched kernel to verify the fix. 3. Construct a BR/EDR L2CAP signaling packet on the fixed channel CID 0x0001 that is larger than MTUsig (e.g., 681 bytes total). The packet should contain a sequence of L2CAP_ECHO_REQ commands; the aim is to cause the victim to respond with many L2CAP_ECHO_RSP frames if the packet is processed. 4. Transmit the oversized signaling packet to the victim before pairing. 5. Observe the victim with btmon/kernel logs. On a vulnerable kernel, the victim may emit a flood of ECHO_RSP frames (e.g., 168 responses) within roughly 200 ms, indicating a DoS condition. On the patched kernel, the packet should be rejected with L2CAP_REJ_MT_EXCEEDED and no ECHO_RSPs. 6. Optional: monitor CPU usage and L2CAP signaling metrics to quantify the impact. Note: Conduct this test only in an isolated lab with explicit authorization. The exact construction of the signaling payload depends on your Bluetooth controller/stack; the critical behavior is that an oversized BR/EDR signaling packet is rejected with MTU_EXCEEDED before command processing.

Commit Details

Author: Michael Bommarito

Date: 2026-05-21 14:45 UTC

Message:

Bluetooth: L2CAP: reject BR/EDR signaling packets over MTUsig 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>

Triage Assessment

Vulnerability Type: Denial of Service

Confidence: HIGH

Reasoning:

The commit adds a guard that rejects BR/EDR signaling packets larger than the BR/EDR signaling MTU (MTUsig) and responds with a MTU_EXCEEDED rejection. This mitigates a DoS vector where an attacker could cause excessive signaling traffic (ECHO_REQ/RSP) by crafting oversized signaling packets, by validating input and enforcing MTU before processing. The change is a concrete security mitigation rather than a pure refactor or documentation effort.

Verification Assessment

Vulnerability Type: Denial of Service

Confidence: HIGH

Affected Versions: < v7.0-rc6 (pre-patch)

Code Diff

diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h index 5172afee5494332..e0a1f2293679af6 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 45b175399e8dbbf..c4ccfbda9d7890c 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -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;
← Back to Alerts View on GitHub →