diff options
Diffstat (limited to 'net/ipv4/igmp.c')
| -rw-r--r-- | net/ipv4/igmp.c | 162 | 
1 files changed, 162 insertions, 0 deletions
diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c index a3a697f5ffba..651cdf648ec4 100644 --- a/net/ipv4/igmp.c +++ b/net/ipv4/igmp.c @@ -1339,6 +1339,168 @@ out:  }  EXPORT_SYMBOL(ip_mc_inc_group); +static int ip_mc_check_iphdr(struct sk_buff *skb) +{ +	const struct iphdr *iph; +	unsigned int len; +	unsigned int offset = skb_network_offset(skb) + sizeof(*iph); + +	if (!pskb_may_pull(skb, offset)) +		return -EINVAL; + +	iph = ip_hdr(skb); + +	if (iph->version != 4 || ip_hdrlen(skb) < sizeof(*iph)) +		return -EINVAL; + +	offset += ip_hdrlen(skb) - sizeof(*iph); + +	if (!pskb_may_pull(skb, offset)) +		return -EINVAL; + +	iph = ip_hdr(skb); + +	if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl))) +		return -EINVAL; + +	len = skb_network_offset(skb) + ntohs(iph->tot_len); +	if (skb->len < len || len < offset) +		return -EINVAL; + +	skb_set_transport_header(skb, offset); + +	return 0; +} + +static int ip_mc_check_igmp_reportv3(struct sk_buff *skb) +{ +	unsigned int len = skb_transport_offset(skb); + +	len += sizeof(struct igmpv3_report); + +	return pskb_may_pull(skb, len) ? 0 : -EINVAL; +} + +static int ip_mc_check_igmp_query(struct sk_buff *skb) +{ +	unsigned int len = skb_transport_offset(skb); + +	len += sizeof(struct igmphdr); +	if (skb->len < len) +		return -EINVAL; + +	/* IGMPv{1,2}? */ +	if (skb->len != len) { +		/* or IGMPv3? */ +		len += sizeof(struct igmpv3_query) - sizeof(struct igmphdr); +		if (skb->len < len || !pskb_may_pull(skb, len)) +			return -EINVAL; +	} + +	/* RFC2236+RFC3376 (IGMPv2+IGMPv3) require the multicast link layer +	 * all-systems destination addresses (224.0.0.1) for general queries +	 */ +	if (!igmp_hdr(skb)->group && +	    ip_hdr(skb)->daddr != htonl(INADDR_ALLHOSTS_GROUP)) +		return -EINVAL; + +	return 0; +} + +static int ip_mc_check_igmp_msg(struct sk_buff *skb) +{ +	switch (igmp_hdr(skb)->type) { +	case IGMP_HOST_LEAVE_MESSAGE: +	case IGMP_HOST_MEMBERSHIP_REPORT: +	case IGMPV2_HOST_MEMBERSHIP_REPORT: +		/* fall through */ +		return 0; +	case IGMPV3_HOST_MEMBERSHIP_REPORT: +		return ip_mc_check_igmp_reportv3(skb); +	case IGMP_HOST_MEMBERSHIP_QUERY: +		return ip_mc_check_igmp_query(skb); +	default: +		return -ENOMSG; +	} +} + +static inline __sum16 ip_mc_validate_checksum(struct sk_buff *skb) +{ +	return skb_checksum_simple_validate(skb); +} + +static int __ip_mc_check_igmp(struct sk_buff *skb, struct sk_buff **skb_trimmed) + +{ +	struct sk_buff *skb_chk; +	unsigned int transport_len; +	unsigned int len = skb_transport_offset(skb) + sizeof(struct igmphdr); +	int ret; + +	transport_len = ntohs(ip_hdr(skb)->tot_len) - ip_hdrlen(skb); + +	skb_get(skb); +	skb_chk = skb_checksum_trimmed(skb, transport_len, +				       ip_mc_validate_checksum); +	if (!skb_chk) +		return -EINVAL; + +	if (!pskb_may_pull(skb_chk, len)) { +		kfree_skb(skb_chk); +		return -EINVAL; +	} + +	ret = ip_mc_check_igmp_msg(skb_chk); +	if (ret) { +		kfree_skb(skb_chk); +		return ret; +	} + +	if (skb_trimmed) +		*skb_trimmed = skb_chk; +	else +		kfree_skb(skb_chk); + +	return 0; +} + +/** + * ip_mc_check_igmp - checks whether this is a sane IGMP packet + * @skb: the skb to validate + * @skb_trimmed: to store an skb pointer trimmed to IPv4 packet tail (optional) + * + * Checks whether an IPv4 packet is a valid IGMP packet. If so sets + * skb network and transport headers accordingly and returns zero. + * + * -EINVAL: A broken packet was detected, i.e. it violates some internet + *  standard + * -ENOMSG: IP header validation succeeded but it is not an IGMP packet. + * -ENOMEM: A memory allocation failure happened. + * + * Optionally, an skb pointer might be provided via skb_trimmed (or set it + * to NULL): After parsing an IGMP packet successfully it will point to + * an skb which has its tail aligned to the IP packet end. This might + * either be the originally provided skb or a trimmed, cloned version if + * the skb frame had data beyond the IP packet. A cloned skb allows us + * to leave the original skb and its full frame unchanged (which might be + * desirable for layer 2 frame jugglers). + * + * The caller needs to release a reference count from any returned skb_trimmed. + */ +int ip_mc_check_igmp(struct sk_buff *skb, struct sk_buff **skb_trimmed) +{ +	int ret = ip_mc_check_iphdr(skb); + +	if (ret < 0) +		return ret; + +	if (ip_hdr(skb)->protocol != IPPROTO_IGMP) +		return -ENOMSG; + +	return __ip_mc_check_igmp(skb, skb_trimmed); +} +EXPORT_SYMBOL(ip_mc_check_igmp); +  /*   *	Resend IGMP JOIN report; used by netdev notifier.   */  |