Bluetooth: L2CAP: Fix handling fragmented length
authorLuiz Augusto von Dentz <luiz.von.dentz@intel.com>
Wed, 13 Jan 2021 23:28:58 +0000 (15:28 -0800)
committerMarcel Holtmann <marcel@holtmann.org>
Mon, 25 Jan 2021 18:26:33 +0000 (19:26 +0100)
Bluetooth Core Specification v5.2, Vol. 3, Part A, section 1.4, table
1.1:

 'Start Fragments always either begin with the first octet of the Basic
  L2CAP header of a PDU or they have a length of zero (see [Vol 2] Part
  B, Section 6.6.2).'

Apparently this was changed by the following errata:

https://www.bluetooth.org/tse/errata_view.cfm?errata_id=10216

Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
include/net/bluetooth/l2cap.h
net/bluetooth/l2cap_core.c

index 1d12329..61800a7 100644 (file)
@@ -207,6 +207,7 @@ struct l2cap_hdr {
        __le16     len;
        __le16     cid;
 } __packed;
+#define L2CAP_LEN_SIZE         2
 #define L2CAP_HDR_SIZE         4
 #define L2CAP_ENH_HDR_SIZE     6
 #define L2CAP_EXT_HDR_SIZE     8
index 17b87b5..a241837 100644 (file)
@@ -8276,10 +8276,73 @@ static void l2cap_security_cfm(struct hci_conn *hcon, u8 status, u8 encrypt)
        mutex_unlock(&conn->chan_lock);
 }
 
+/* Append fragment into frame respecting the maximum len of rx_skb */
+static int l2cap_recv_frag(struct l2cap_conn *conn, struct sk_buff *skb,
+                          u16 len)
+{
+       if (!conn->rx_skb) {
+               /* Allocate skb for the complete frame (with header) */
+               conn->rx_skb = bt_skb_alloc(len, GFP_KERNEL);
+               if (!conn->rx_skb)
+                       return -ENOMEM;
+               /* Init rx_len */
+               conn->rx_len = len;
+       }
+
+       /* Copy as much as the rx_skb can hold */
+       len = min_t(u16, len, skb->len);
+       skb_copy_from_linear_data(skb, skb_put(conn->rx_skb, len), len);
+       skb_pull(skb, len);
+       conn->rx_len -= len;
+
+       return len;
+}
+
+static int l2cap_recv_len(struct l2cap_conn *conn, struct sk_buff *skb)
+{
+       struct sk_buff *rx_skb;
+       int len;
+
+       /* Append just enough to complete the header */
+       len = l2cap_recv_frag(conn, skb, L2CAP_LEN_SIZE - conn->rx_skb->len);
+
+       /* If header could not be read just continue */
+       if (len < 0 || conn->rx_skb->len < L2CAP_LEN_SIZE)
+               return len;
+
+       rx_skb = conn->rx_skb;
+       len = get_unaligned_le16(rx_skb->data);
+
+       /* Check if rx_skb has enough space to received all fragments */
+       if (len + (L2CAP_HDR_SIZE - L2CAP_LEN_SIZE) <= skb_tailroom(rx_skb)) {
+               /* Update expected len */
+               conn->rx_len = len + (L2CAP_HDR_SIZE - L2CAP_LEN_SIZE);
+               return L2CAP_LEN_SIZE;
+       }
+
+       /* Reset conn->rx_skb since it will need to be reallocated in order to
+        * fit all fragments.
+        */
+       conn->rx_skb = NULL;
+
+       /* Reallocates rx_skb using the exact expected length */
+       len = l2cap_recv_frag(conn, rx_skb,
+                             len + (L2CAP_HDR_SIZE - L2CAP_LEN_SIZE));
+       kfree_skb(rx_skb);
+
+       return len;
+}
+
+static void l2cap_recv_reset(struct l2cap_conn *conn)
+{
+       kfree_skb(conn->rx_skb);
+       conn->rx_skb = NULL;
+       conn->rx_len = 0;
+}
+
 void l2cap_recv_acldata(struct hci_conn *hcon, struct sk_buff *skb, u16 flags)
 {
        struct l2cap_conn *conn = hcon->l2cap_data;
-       struct l2cap_hdr *hdr;
        int len;
 
        /* For AMP controller do not create l2cap conn */
@@ -8298,23 +8361,23 @@ void l2cap_recv_acldata(struct hci_conn *hcon, struct sk_buff *skb, u16 flags)
        case ACL_START:
        case ACL_START_NO_FLUSH:
        case ACL_COMPLETE:
-               if (conn->rx_len) {
+               if (conn->rx_skb) {
                        BT_ERR("Unexpected start frame (len %d)", skb->len);
-                       kfree_skb(conn->rx_skb);
-                       conn->rx_skb = NULL;
-                       conn->rx_len = 0;
+                       l2cap_recv_reset(conn);
                        l2cap_conn_unreliable(conn, ECOMM);
                }
 
-               /* Start fragment always begin with Basic L2CAP header */
-               if (skb->len < L2CAP_HDR_SIZE) {
-                       BT_ERR("Frame is too short (len %d)", skb->len);
-                       l2cap_conn_unreliable(conn, ECOMM);
-                       goto drop;
+               /* Start fragment may not contain the L2CAP length so just
+                * copy the initial byte when that happens and use conn->mtu as
+                * expected length.
+                */
+               if (skb->len < L2CAP_LEN_SIZE) {
+                       if (l2cap_recv_frag(conn, skb, conn->mtu) < 0)
+                               goto drop;
+                       return;
                }
 
-               hdr = (struct l2cap_hdr *) skb->data;
-               len = __le16_to_cpu(hdr->len) + L2CAP_HDR_SIZE;
+               len = get_unaligned_le16(skb->data) + L2CAP_HDR_SIZE;
 
                if (len == skb->len) {
                        /* Complete frame received */
@@ -8331,38 +8394,43 @@ void l2cap_recv_acldata(struct hci_conn *hcon, struct sk_buff *skb, u16 flags)
                        goto drop;
                }
 
-               /* Allocate skb for the complete frame (with header) */
-               conn->rx_skb = bt_skb_alloc(len, GFP_KERNEL);
-               if (!conn->rx_skb)
+               /* Append fragment into frame (with header) */
+               if (l2cap_recv_frag(conn, skb, len) < 0)
                        goto drop;
 
-               skb_copy_from_linear_data(skb, skb_put(conn->rx_skb, skb->len),
-                                         skb->len);
-               conn->rx_len = len - skb->len;
                break;
 
        case ACL_CONT:
                BT_DBG("Cont: frag len %d (expecting %d)", skb->len, conn->rx_len);
 
-               if (!conn->rx_len) {
+               if (!conn->rx_skb) {
                        BT_ERR("Unexpected continuation frame (len %d)", skb->len);
                        l2cap_conn_unreliable(conn, ECOMM);
                        goto drop;
                }
 
+               /* Complete the L2CAP length if it has not been read */
+               if (conn->rx_skb->len < L2CAP_LEN_SIZE) {
+                       if (l2cap_recv_len(conn, skb) < 0) {
+                               l2cap_conn_unreliable(conn, ECOMM);
+                               goto drop;
+                       }
+
+                       /* Header still could not be read just continue */
+                       if (conn->rx_skb->len < L2CAP_LEN_SIZE)
+                               return;
+               }
+
                if (skb->len > conn->rx_len) {
                        BT_ERR("Fragment is too long (len %d, expected %d)",
                               skb->len, conn->rx_len);
-                       kfree_skb(conn->rx_skb);
-                       conn->rx_skb = NULL;
-                       conn->rx_len = 0;
+                       l2cap_recv_reset(conn);
                        l2cap_conn_unreliable(conn, ECOMM);
                        goto drop;
                }
 
-               skb_copy_from_linear_data(skb, skb_put(conn->rx_skb, skb->len),
-                                         skb->len);
-               conn->rx_len -= skb->len;
+               /* Append fragment into frame (with header) */
+               l2cap_recv_frag(conn, skb, skb->len);
 
                if (!conn->rx_len) {
                        /* Complete frame received. l2cap_recv_frame