Skip to content

Commit

Permalink
Bluetooth: L2CAP: Fix handling fragmented length
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Vudentz authored and holtmann committed Jan 25, 2021
1 parent 5ff20cb commit 4d7ea8e
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 25 deletions.
1 change: 1 addition & 0 deletions include/net/bluetooth/l2cap.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
118 changes: 93 additions & 25 deletions net/bluetooth/l2cap_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand All @@ -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 */
Expand All @@ -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
Expand Down

0 comments on commit 4d7ea8e

Please sign in to comment.