Commit message: NFC: llcp: Limit size of SDP URI

The tlv_len is u8, so we need to limit the size of the SDP URI. Enforce
this both in the NLA policy and in the code that performs the allocation
and copy, to avoid writing past the end of the allocated buffer.

Fixes: d9b8d8e19b073 ("NFC: llcp: Service Name Lookup netlink interface")
Signed-off-by: Kees Cook <keescook@chromium.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
CVE: CVE-2018-9518
CWE: CWE-787
Vulnerability description: In nfc_llcp_build_sdreq_tlv of llcp_commands.c, there is a possible out of bounds write due to a missing bounds check. This could lead to local escalation of privilege with System execution privileges needed. User interaction is not needed for exploitation. Product: Android. Versions: Android kernel. Android ID: A-73083945.

Project: linux
File: llcp_commands.c

int nfc_llcp_send_cc(struct nfc_llcp_sock *sock)
{
	struct nfc_llcp_local *local;
	struct sk_buff *skb;
	u8 *miux_tlv = NULL, miux_tlv_length;
	u8 *rw_tlv = NULL, rw_tlv_length, rw;
	int err;
	u16 size = 0;
	__be16 miux;

	pr_debug("Sending CC\n");

	local = sock->local;
	if (local == NULL)
		return -ENODEV;

	/* If the socket parameters are not set, use the local ones */
	miux = be16_to_cpu(sock->miux) > LLCP_MAX_MIUX ?
		local->miux : sock->miux;
	rw = sock->rw > LLCP_MAX_RW ? local->rw : sock->rw;

	miux_tlv = nfc_llcp_build_tlv(LLCP_TLV_MIUX, (u8 *)&miux, 0,
				      &miux_tlv_length);
	size += miux_tlv_length;

	rw_tlv = nfc_llcp_build_tlv(LLCP_TLV_RW, &rw, 0, &rw_tlv_length);
	size += rw_tlv_length;

	skb = llcp_allocate_pdu(sock, LLCP_PDU_CC, size);
	if (skb == NULL) {
		err = -ENOMEM;
		goto error_tlv;
	}

	llcp_add_tlv(skb, miux_tlv, miux_tlv_length);
	llcp_add_tlv(skb, rw_tlv, rw_tlv_length);

	skb_queue_tail(&local->tx_queue, skb);

	err = 0;

error_tlv:
	if (err)
		pr_err("error %d\n", err);

	kfree(miux_tlv);
	kfree(rw_tlv);

	return err;
}

Project: linux
File: llcp_commands.c

int nfc_llcp_send_symm(struct nfc_dev *dev)
{
	struct sk_buff *skb;
	struct nfc_llcp_local *local;
	u16 size = 0;

	pr_debug("Sending SYMM\n");

	local = nfc_llcp_find_local(dev);
	if (local == NULL)
		return -ENODEV;

	size += LLCP_HEADER_SIZE;
	size += dev->tx_headroom + dev->tx_tailroom + NFC_HEADER_SIZE;

	skb = alloc_skb(size, GFP_KERNEL);
	if (skb == NULL)
		return -ENOMEM;

	skb_reserve(skb, dev->tx_headroom + NFC_HEADER_SIZE);

	skb = llcp_add_header(skb, 0, 0, LLCP_PDU_SYMM);

	__net_timestamp(skb);

	nfc_llcp_send_to_raw_sock(local, skb, NFC_DIRECTION_TX);

	return nfc_data_exchange(dev, local->target_idx, skb,
				 nfc_llcp_recv, local);
}

Project: linux
File: llcp_commands.c

int nfc_llcp_send_connect(struct nfc_llcp_sock *sock)
{
	struct nfc_llcp_local *local;
	struct sk_buff *skb;
	u8 *service_name_tlv = NULL, service_name_tlv_length;
	u8 *miux_tlv = NULL, miux_tlv_length;
	u8 *rw_tlv = NULL, rw_tlv_length, rw;
	int err;
	u16 size = 0;
	__be16 miux;

	pr_debug("Sending CONNECT\n");

	local = sock->local;
	if (local == NULL)
		return -ENODEV;

	if (sock->service_name != NULL) {
		service_name_tlv = nfc_llcp_build_tlv(LLCP_TLV_SN,
						      sock->service_name,
						      sock->service_name_len,
						      &service_name_tlv_length);
		size += service_name_tlv_length;
	}

	/* If the socket parameters are not set, use the local ones */
	miux = be16_to_cpu(sock->miux) > LLCP_MAX_MIUX ?
		local->miux : sock->miux;
	rw = sock->rw > LLCP_MAX_RW ? local->rw : sock->rw;

	miux_tlv = nfc_llcp_build_tlv(LLCP_TLV_MIUX, (u8 *)&miux, 0,
				      &miux_tlv_length);
	size += miux_tlv_length;

	rw_tlv = nfc_llcp_build_tlv(LLCP_TLV_RW, &rw, 0, &rw_tlv_length);
	size += rw_tlv_length;

	pr_debug("SKB size %d SN length %zu\n", size, sock->service_name_len);

	skb = llcp_allocate_pdu(sock, LLCP_PDU_CONNECT, size);
	if (skb == NULL) {
		err = -ENOMEM;
		goto error_tlv;
	}

	llcp_add_tlv(skb, service_name_tlv, service_name_tlv_length);
	llcp_add_tlv(skb, miux_tlv, miux_tlv_length);
	llcp_add_tlv(skb, rw_tlv, rw_tlv_length);

	skb_queue_tail(&local->tx_queue, skb);

	err = 0;

error_tlv:
	if (err)
		pr_err("error %d\n", err);

	kfree(service_name_tlv);
	kfree(miux_tlv);
	kfree(rw_tlv);

	return err;
}

Project: linux
File: llcp_commands.c

static u8 llcp_tlv_opt(u8 *tlv)
{
	return llcp_tlv8(tlv, LLCP_TLV_OPT);
}

Project: linux
File: llcp_commands.c

int nfc_llcp_send_ui_frame(struct nfc_llcp_sock *sock, u8 ssap, u8 dsap,
			   struct msghdr *msg, size_t len)
{
	struct sk_buff *pdu;
	struct nfc_llcp_local *local;
	size_t frag_len = 0, remaining_len;
	u8 *msg_ptr, *msg_data;
	u16 remote_miu;
	int err;

	pr_debug("Send UI frame len %zd\n", len);

	local = sock->local;
	if (local == NULL)
		return -ENODEV;

	msg_data = kmalloc(len, GFP_USER | __GFP_NOWARN);
	if (msg_data == NULL)
		return -ENOMEM;

	if (memcpy_from_msg(msg_data, msg, len)) {
		kfree(msg_data);
		return -EFAULT;
	}

	remaining_len = len;
	msg_ptr = msg_data;

	do {
		remote_miu = sock->remote_miu > LLCP_MAX_MIU ?
				local->remote_miu : sock->remote_miu;

		frag_len = min_t(size_t, remote_miu, remaining_len);

		pr_debug("Fragment %zd bytes remaining %zd",
			 frag_len, remaining_len);

		pdu = nfc_alloc_send_skb(sock->dev, &sock->sk, MSG_DONTWAIT,
					 frag_len + LLCP_HEADER_SIZE, &err);
		if (pdu == NULL) {
			pr_err("Could not allocate PDU\n");
			continue;
		}

		pdu = llcp_add_header(pdu, dsap, ssap, LLCP_PDU_UI);

		if (likely(frag_len > 0))
			skb_put_data(pdu, msg_ptr, frag_len);

		/* No need to check for the peer RW for UI frames */
		skb_queue_tail(&local->tx_queue, pdu);

		remaining_len -= frag_len;
		msg_ptr += frag_len;
	} while (remaining_len > 0);

	kfree(msg_data);

	return len;
}