Merge tag 'dma-buf-for-4.2' of git://git.kernel.org/pub/scm/linux/kernel/git/sumits...
[cascardo/linux.git] / net / ipv4 / igmp.c
index a3a697f..651cdf6 100644 (file)
@@ -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.
  */