mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 02:30:34 +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     len;
 | 
				
			||||||
	__le16     cid;
 | 
						__le16     cid;
 | 
				
			||||||
} __packed;
 | 
					} __packed;
 | 
				
			||||||
 | 
					#define L2CAP_LEN_SIZE		2
 | 
				
			||||||
#define L2CAP_HDR_SIZE		4
 | 
					#define L2CAP_HDR_SIZE		4
 | 
				
			||||||
#define L2CAP_ENH_HDR_SIZE	6
 | 
					#define L2CAP_ENH_HDR_SIZE	6
 | 
				
			||||||
#define L2CAP_EXT_HDR_SIZE	8
 | 
					#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);
 | 
						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)
 | 
					void l2cap_recv_acldata(struct hci_conn *hcon, struct sk_buff *skb, u16 flags)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct l2cap_conn *conn = hcon->l2cap_data;
 | 
						struct l2cap_conn *conn = hcon->l2cap_data;
 | 
				
			||||||
	struct l2cap_hdr *hdr;
 | 
					 | 
				
			||||||
	int len;
 | 
						int len;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/* For AMP controller do not create l2cap conn */
 | 
						/* 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:
 | 
				
			||||||
	case ACL_START_NO_FLUSH:
 | 
						case ACL_START_NO_FLUSH:
 | 
				
			||||||
	case ACL_COMPLETE:
 | 
						case ACL_COMPLETE:
 | 
				
			||||||
		if (conn->rx_len) {
 | 
							if (conn->rx_skb) {
 | 
				
			||||||
			BT_ERR("Unexpected start frame (len %d)", skb->len);
 | 
								BT_ERR("Unexpected start frame (len %d)", skb->len);
 | 
				
			||||||
			kfree_skb(conn->rx_skb);
 | 
								l2cap_recv_reset(conn);
 | 
				
			||||||
			conn->rx_skb = NULL;
 | 
					 | 
				
			||||||
			conn->rx_len = 0;
 | 
					 | 
				
			||||||
			l2cap_conn_unreliable(conn, ECOMM);
 | 
								l2cap_conn_unreliable(conn, ECOMM);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/* Start fragment always begin with Basic L2CAP header */
 | 
							/* Start fragment may not contain the L2CAP length so just
 | 
				
			||||||
		if (skb->len < L2CAP_HDR_SIZE) {
 | 
							 * copy the initial byte when that happens and use conn->mtu as
 | 
				
			||||||
			BT_ERR("Frame is too short (len %d)", skb->len);
 | 
							 * expected length.
 | 
				
			||||||
			l2cap_conn_unreliable(conn, ECOMM);
 | 
							 */
 | 
				
			||||||
 | 
							if (skb->len < L2CAP_LEN_SIZE) {
 | 
				
			||||||
 | 
								if (l2cap_recv_frag(conn, skb, conn->mtu) < 0)
 | 
				
			||||||
				goto drop;
 | 
									goto drop;
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		hdr = (struct l2cap_hdr *) skb->data;
 | 
							len = get_unaligned_le16(skb->data) + L2CAP_HDR_SIZE;
 | 
				
			||||||
		len = __le16_to_cpu(hdr->len) + L2CAP_HDR_SIZE;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (len == skb->len) {
 | 
							if (len == skb->len) {
 | 
				
			||||||
			/* Complete frame received */
 | 
								/* Complete frame received */
 | 
				
			||||||
| 
						 | 
					@ -8331,38 +8394,43 @@ void l2cap_recv_acldata(struct hci_conn *hcon, struct sk_buff *skb, u16 flags)
 | 
				
			||||||
			goto drop;
 | 
								goto drop;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/* Allocate skb for the complete frame (with header) */
 | 
							/* Append fragment into frame (with header) */
 | 
				
			||||||
		conn->rx_skb = bt_skb_alloc(len, GFP_KERNEL);
 | 
							if (l2cap_recv_frag(conn, skb, len) < 0)
 | 
				
			||||||
		if (!conn->rx_skb)
 | 
					 | 
				
			||||||
			goto drop;
 | 
								goto drop;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		skb_copy_from_linear_data(skb, skb_put(conn->rx_skb, skb->len),
 | 
					 | 
				
			||||||
					  skb->len);
 | 
					 | 
				
			||||||
		conn->rx_len = len - skb->len;
 | 
					 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	case ACL_CONT:
 | 
						case ACL_CONT:
 | 
				
			||||||
		BT_DBG("Cont: frag len %d (expecting %d)", skb->len, conn->rx_len);
 | 
							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);
 | 
								BT_ERR("Unexpected continuation frame (len %d)", skb->len);
 | 
				
			||||||
			l2cap_conn_unreliable(conn, ECOMM);
 | 
								l2cap_conn_unreliable(conn, ECOMM);
 | 
				
			||||||
			goto drop;
 | 
								goto drop;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (skb->len > conn->rx_len) {
 | 
							/* Complete the L2CAP length if it has not been read */
 | 
				
			||||||
			BT_ERR("Fragment is too long (len %d, expected %d)",
 | 
							if (conn->rx_skb->len < L2CAP_LEN_SIZE) {
 | 
				
			||||||
			       skb->len, conn->rx_len);
 | 
								if (l2cap_recv_len(conn, skb) < 0) {
 | 
				
			||||||
			kfree_skb(conn->rx_skb);
 | 
					 | 
				
			||||||
			conn->rx_skb = NULL;
 | 
					 | 
				
			||||||
			conn->rx_len = 0;
 | 
					 | 
				
			||||||
				l2cap_conn_unreliable(conn, ECOMM);
 | 
									l2cap_conn_unreliable(conn, ECOMM);
 | 
				
			||||||
				goto drop;
 | 
									goto drop;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		skb_copy_from_linear_data(skb, skb_put(conn->rx_skb, skb->len),
 | 
								/* Header still could not be read just continue */
 | 
				
			||||||
					  skb->len);
 | 
								if (conn->rx_skb->len < L2CAP_LEN_SIZE)
 | 
				
			||||||
		conn->rx_len -= skb->len;
 | 
									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) {
 | 
							if (!conn->rx_len) {
 | 
				
			||||||
			/* Complete frame received. l2cap_recv_frame
 | 
								/* Complete frame received. l2cap_recv_frame
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue