mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	Bluetooth: L2CAP: Fix handling fragmented length
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>
This commit is contained in:
		
							parent
							
								
									5ff20cbe67
								
							
						
					
					
						commit
						4d7ea8ee90
					
				
					 2 changed files with 94 additions and 25 deletions
				
			
		| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
		/* 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;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		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;
 | 
			
		||||
		/* 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;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		skb_copy_from_linear_data(skb, skb_put(conn->rx_skb, skb->len),
 | 
			
		||||
					  skb->len);
 | 
			
		||||
		conn->rx_len -= skb->len;
 | 
			
		||||
			/* 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);
 | 
			
		||||
			l2cap_recv_reset(conn);
 | 
			
		||||
			l2cap_conn_unreliable(conn, ECOMM);
 | 
			
		||||
			goto drop;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/* Append fragment into frame (with header) */
 | 
			
		||||
		l2cap_recv_frag(conn, skb, skb->len);
 | 
			
		||||
 | 
			
		||||
		if (!conn->rx_len) {
 | 
			
		||||
			/* Complete frame received. l2cap_recv_frame
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue