[PATCH AUTOSEL 6.18-5.10] Bluetooth: L2CAP: validate connectionless PSM length

From: Sasha Levin

Date: Mon Aug 31 2026 - 10:26:42 EST


From: Samuel Moelius <sam.moelius@xxxxxxxxxxxxxxx>

[ Upstream commit a40a5f922546b3bd7c094d882b29177db4f2abe0 ]

Connectionless L2CAP frames carry a two-byte PSM at the start of the
payload. l2cap_recv_frame() currently reads that PSM unconditionally
after validating only the outer L2CAP length.

A malformed connectionless frame with a zero- or one-byte payload can
therefore make the parser read beyond the advertised skb payload and use
tailroom bytes as part of the PSM. A VHCI-backed QEMU reproducer
injected a one-byte connectionless payload and reached the unchecked
read.

Reject connectionless frames that cannot contain the PSM before reading
or pulling it. This preserves all valid connectionless frames while
dropping only structurally incomplete packets.

Assisted-by: Codex:gpt-5.5-cyber-preview
Signed-off-by: Samuel Moelius <sam.moelius@xxxxxxxxxxxxxxx>
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@xxxxxxxxx>
Signed-off-by: Sasha Levin <sashal@xxxxxxxxxx>
---

LLM Generated explanations, may be completely bogus:

The background git searches finished: neither `git log --grep="validate
connectionless PSM"` nor `--grep="connectionless PSM length"` found
anything on HEAD, which matches the earlier finding that `a40a5f922546`
is not in the 6.18.44 tree even though the buggy code is still there.

**Verdict for this tree (6.18.44): YES** — backport the 5-line PSM
length check. It fixes a remote-triggerable out-of-bounds read in
connectionless L2CAP handling, applies cleanly, and follows the same
pattern as other L2CAP validation fixes already in this branch.

net/bluetooth/l2cap_core.c | 5 +++++
1 file changed, 5 insertions(+)

diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index 6133c65b20172..1a38a667ef0b0 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -7117,6 +7117,11 @@ static void l2cap_recv_frame(struct l2cap_conn *conn, struct sk_buff *skb)
break;

case L2CAP_CID_CONN_LESS:
+ if (skb->len < L2CAP_PSMLEN_SIZE) {
+ kfree_skb(skb);
+ break;
+ }
+
psm = get_unaligned((__le16 *) skb->data);
skb_pull(skb, L2CAP_PSMLEN_SIZE);
l2cap_conless_channel(conn, psm, skb);
--
2.53.0