Commit message: Cipso: cipso_v4_optptr enter infinite loop

in for(),if((optlen > 0) && (optptr[1] == 0)), enter infinite loop.

Test: receive a packet which the ip length > 20 and the first byte of ip option is 0, produce this issue

Signed-off-by: yujuan.qi <yujuan.qi@mediatek.com>
Acked-by: Paul Moore <paul@paul-moore.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
CVE: CVE-2018-10938
CWE: CWE-835
Vulnerability description: A flaw was found in the Linux kernel present since v4.0-rc1 and through v4.13-rc4. A crafted network packet sent remotely by an attacker may force the kernel to enter an infinite loop in the cipso_v4_optptr() function in net/ipv4/cipso_ipv4.c leading to a denial-of-service. A certain non-default configuration of LSM (Linux Security Module) and NetLabel should be set up on a system before an attacker could leverage this flaw.

Project: linux

void cipso_v4_error(struct sk_buff *skb, int error, u32 gateway)
{
	if (ip_hdr(skb)->protocol == IPPROTO_ICMP || error != -EACCES)
		return;

	if (gateway)
		icmp_send(skb, ICMP_DEST_UNREACH, ICMP_NET_ANO, 0);
	else
		icmp_send(skb, ICMP_DEST_UNREACH, ICMP_HOST_ANO, 0);
}

Project: linux

static int cipso_v4_map_lvl_valid(const struct cipso_v4_doi *doi_def, u8 level)
{
	switch (doi_def->type) {
	case CIPSO_V4_MAP_PASS:
		return 0;
	case CIPSO_V4_MAP_TRANS:
		if (doi_def->map.std->lvl.cipso[level] < CIPSO_V4_INV_LVL)
			return 0;
		break;
	}

	return -EFAULT;
}

Project: linux

int cipso_v4_req_setattr(struct request_sock *req,
			 const struct cipso_v4_doi *doi_def,
			 const struct netlbl_lsm_secattr *secattr)
{
	int ret_val = -EPERM;
	unsigned char *buf = NULL;
	u32 buf_len;
	u32 opt_len;
	struct ip_options_rcu *opt = NULL;
	struct inet_request_sock *req_inet;

	/* We allocate the maximum CIPSO option size here so we are probably
	 * being a little wasteful, but it makes our life _much_ easier later
	 * on and after all we are only talking about 40 bytes. */
	buf_len = CIPSO_V4_OPT_LEN_MAX;
	buf = kmalloc(buf_len, GFP_ATOMIC);
	if (!buf) {
		ret_val = -ENOMEM;
		goto req_setattr_failure;
	}

	ret_val = cipso_v4_genopt(buf, buf_len, doi_def, secattr);
	if (ret_val < 0)
		goto req_setattr_failure;
	buf_len = ret_val;

	/* We can't use ip_options_get() directly because it makes a call to
	 * ip_options_get_alloc() which allocates memory with GFP_KERNEL and
	 * we won't always have CAP_NET_RAW even though we _always_ want to
	 * set the IPOPT_CIPSO option. */
	opt_len = (buf_len + 3) & ~3;
	opt = kzalloc(sizeof(*opt) + opt_len, GFP_ATOMIC);
	if (!opt) {
		ret_val = -ENOMEM;
		goto req_setattr_failure;
	}
	memcpy(opt->opt.__data, buf, buf_len);
	opt->opt.optlen = opt_len;
	opt->opt.cipso = sizeof(struct iphdr);
	kfree(buf);
	buf = NULL;

	req_inet = inet_rsk(req);
	opt = xchg(&req_inet->opt, opt);
	if (opt)
		kfree_rcu(opt, rcu);

	return 0;

req_setattr_failure:
	kfree(buf);
	kfree(opt);
	return ret_val;
}

Project: linux

void cipso_v4_sock_delattr(struct sock *sk)
{
	int hdr_delta;
	struct ip_options_rcu *opt;
	struct inet_sock *sk_inet;

	sk_inet = inet_sk(sk);
	opt = rcu_dereference_protected(sk_inet->inet_opt, 1);
	if (!opt || opt->opt.cipso == 0)
		return;

	hdr_delta = cipso_v4_delopt(&sk_inet->inet_opt);
	if (sk_inet->is_icsk && hdr_delta > 0) {
		struct inet_connection_sock *sk_conn = inet_csk(sk);
		sk_conn->icsk_ext_hdr_len -= hdr_delta;
		sk_conn->icsk_sync_mss(sk, sk_conn->icsk_pmtu_cookie);
	}
}

Project: linux

void cipso_v4_req_delattr(struct request_sock *req)
{
	struct ip_options_rcu *opt;
	struct inet_request_sock *req_inet;

	req_inet = inet_rsk(req);
	opt = req_inet->opt;
	if (!opt || opt->opt.cipso == 0)
		return;

	cipso_v4_delopt(&req_inet->opt);
}

Project: linux

static int cipso_v4_parsetag_rbm(const struct cipso_v4_doi *doi_def,
				 const unsigned char *tag,
				 struct netlbl_lsm_secattr *secattr)
{
	int ret_val;
	u8 tag_len = tag[1];
	u32 level;

	ret_val = cipso_v4_map_lvl_ntoh(doi_def, tag[3], &level);
	if (ret_val != 0)
		return ret_val;
	secattr->attr.mls.lvl = level;
	secattr->flags |= NETLBL_SECATTR_MLS_LVL;

	if (tag_len > 4) {
		ret_val = cipso_v4_map_cat_rbm_ntoh(doi_def,
						    &tag[4],
						    tag_len - 4,
						    secattr);
		if (ret_val != 0) {
			netlbl_catmap_free(secattr->attr.mls.cat);
			return ret_val;
		}

		secattr->flags |= NETLBL_SECATTR_MLS_CAT;
	}

	return 0;
}

Project: linux

static int cipso_v4_parsetag_rng(const struct cipso_v4_doi *doi_def,
				 const unsigned char *tag,
				 struct netlbl_lsm_secattr *secattr)
{
	int ret_val;
	u8 tag_len = tag[1];
	u32 level;

	ret_val = cipso_v4_map_lvl_ntoh(doi_def, tag[3], &level);
	if (ret_val != 0)
		return ret_val;
	secattr->attr.mls.lvl = level;
	secattr->flags |= NETLBL_SECATTR_MLS_LVL;

	if (tag_len > 4) {
		ret_val = cipso_v4_map_cat_rng_ntoh(doi_def,
						    &tag[4],
						    tag_len - 4,
						    secattr);
		if (ret_val != 0) {
			netlbl_catmap_free(secattr->attr.mls.cat);
			return ret_val;
		}

		secattr->flags |= NETLBL_SECATTR_MLS_CAT;
	}

	return 0;
}

Project: linux

static int cipso_v4_delopt(struct ip_options_rcu **opt_ptr)
{
	int hdr_delta = 0;
	struct ip_options_rcu *opt = *opt_ptr;

	if (opt->opt.srr || opt->opt.rr || opt->opt.ts || opt->opt.router_alert) {
		u8 cipso_len;
		u8 cipso_off;
		unsigned char *cipso_ptr;
		int iter;
		int optlen_new;

		cipso_off = opt->opt.cipso - sizeof(struct iphdr);
		cipso_ptr = &opt->opt.__data[cipso_off];
		cipso_len = cipso_ptr[1];

		if (opt->opt.srr > opt->opt.cipso)
			opt->opt.srr -= cipso_len;
		if (opt->opt.rr > opt->opt.cipso)
			opt->opt.rr -= cipso_len;
		if (opt->opt.ts > opt->opt.cipso)
			opt->opt.ts -= cipso_len;
		if (opt->opt.router_alert > opt->opt.cipso)
			opt->opt.router_alert -= cipso_len;
		opt->opt.cipso = 0;

		memmove(cipso_ptr, cipso_ptr + cipso_len,
			opt->opt.optlen - cipso_off - cipso_len);

		/* determining the new total option length is tricky because of
		 * the padding necessary, the only thing i can think to do at
		 * this point is walk the options one-by-one, skipping the
		 * padding at the end to determine the actual option size and
		 * from there we can determine the new total option length */
		iter = 0;
		optlen_new = 0;
		while (iter < opt->opt.optlen)
			if (opt->opt.__data[iter] != IPOPT_NOP) {
				iter += opt->opt.__data[iter + 1];
				optlen_new = iter;
			} else
				iter++;
		hdr_delta = opt->opt.optlen;
		opt->opt.optlen = (optlen_new + 3) & ~3;
		hdr_delta -= opt->opt.optlen;
	} else {
		/* only the cipso option was present on the socket so we can
		 * remove the entire option struct */
		*opt_ptr = NULL;
		hdr_delta = opt->opt.optlen;
		kfree_rcu(opt, rcu);
	}

	return hdr_delta;
}

Project: linux

unsigned char *cipso_v4_optptr(const struct sk_buff *skb)
{
	const struct iphdr *iph = ip_hdr(skb);
	unsigned char *optptr = (unsigned char *)&(ip_hdr(skb)[1]);
	int optlen;
	int taglen;

	for (optlen = iph->ihl*4 - sizeof(struct iphdr); optlen > 0; ) {
		switch (optptr[0]) {
		case IPOPT_CIPSO:
			return optptr;
		case IPOPT_END:
			return NULL;
		case IPOPT_NOOP:
			taglen = 1;
			break;
		default:
			taglen = optptr[1];
		}
		optlen -= taglen;
		optptr += taglen;
	}

	return NULL;
}