Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-2.0-only 2 : : /* Copyright (C) 2010: YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org> 3 : : * Copyright (C) 2015: Linus Lüssing <linus.luessing@c0d3.blue> 4 : : * 5 : : * Based on the MLD support added to br_multicast.c by YOSHIFUJI Hideaki. 6 : : */ 7 : : 8 : : #include <linux/skbuff.h> 9 : : #include <net/ipv6.h> 10 : : #include <net/mld.h> 11 : : #include <net/addrconf.h> 12 : : #include <net/ip6_checksum.h> 13 : : 14 : 0 : static int ipv6_mc_check_ip6hdr(struct sk_buff *skb) 15 : : { 16 : : const struct ipv6hdr *ip6h; 17 : : unsigned int len; 18 : 0 : unsigned int offset = skb_network_offset(skb) + sizeof(*ip6h); 19 : : 20 : 0 : if (!pskb_may_pull(skb, offset)) 21 : : return -EINVAL; 22 : : 23 : : ip6h = ipv6_hdr(skb); 24 : : 25 : 0 : if (ip6h->version != 6) 26 : : return -EINVAL; 27 : : 28 : 0 : len = offset + ntohs(ip6h->payload_len); 29 : 0 : if (skb->len < len || len <= offset) 30 : : return -EINVAL; 31 : : 32 : : skb_set_transport_header(skb, offset); 33 : : 34 : 0 : return 0; 35 : : } 36 : : 37 : 0 : static int ipv6_mc_check_exthdrs(struct sk_buff *skb) 38 : : { 39 : : const struct ipv6hdr *ip6h; 40 : : int offset; 41 : : u8 nexthdr; 42 : : __be16 frag_off; 43 : : 44 : : ip6h = ipv6_hdr(skb); 45 : : 46 : 0 : if (ip6h->nexthdr != IPPROTO_HOPOPTS) 47 : : return -ENOMSG; 48 : : 49 : 0 : nexthdr = ip6h->nexthdr; 50 : 0 : offset = skb_network_offset(skb) + sizeof(*ip6h); 51 : 0 : offset = ipv6_skip_exthdr(skb, offset, &nexthdr, &frag_off); 52 : : 53 : 0 : if (offset < 0) 54 : : return -EINVAL; 55 : : 56 : 0 : if (nexthdr != IPPROTO_ICMPV6) 57 : : return -ENOMSG; 58 : : 59 : : skb_set_transport_header(skb, offset); 60 : : 61 : 0 : return 0; 62 : : } 63 : : 64 : 0 : static int ipv6_mc_check_mld_reportv2(struct sk_buff *skb) 65 : : { 66 : 0 : unsigned int len = skb_transport_offset(skb); 67 : : 68 : 0 : len += sizeof(struct mld2_report); 69 : : 70 : 0 : return ipv6_mc_may_pull(skb, len) ? 0 : -EINVAL; 71 : : } 72 : : 73 : 0 : static int ipv6_mc_check_mld_query(struct sk_buff *skb) 74 : : { 75 : : unsigned int transport_len = ipv6_transport_len(skb); 76 : : struct mld_msg *mld; 77 : : unsigned int len; 78 : : 79 : : /* RFC2710+RFC3810 (MLDv1+MLDv2) require link-local source addresses */ 80 : 0 : if (!(ipv6_addr_type(&ipv6_hdr(skb)->saddr) & IPV6_ADDR_LINKLOCAL)) 81 : : return -EINVAL; 82 : : 83 : : /* MLDv1? */ 84 : 0 : if (transport_len != sizeof(struct mld_msg)) { 85 : : /* or MLDv2? */ 86 : 0 : if (transport_len < sizeof(struct mld2_query)) 87 : : return -EINVAL; 88 : : 89 : 0 : len = skb_transport_offset(skb) + sizeof(struct mld2_query); 90 : 0 : if (!ipv6_mc_may_pull(skb, len)) 91 : : return -EINVAL; 92 : : } 93 : : 94 : : mld = (struct mld_msg *)skb_transport_header(skb); 95 : : 96 : : /* RFC2710+RFC3810 (MLDv1+MLDv2) require the multicast link layer 97 : : * all-nodes destination address (ff02::1) for general queries 98 : : */ 99 : 0 : if (ipv6_addr_any(&mld->mld_mca) && 100 : : !ipv6_addr_is_ll_all_nodes(&ipv6_hdr(skb)->daddr)) 101 : : return -EINVAL; 102 : : 103 : 0 : return 0; 104 : : } 105 : : 106 : 0 : static int ipv6_mc_check_mld_msg(struct sk_buff *skb) 107 : : { 108 : 0 : unsigned int len = skb_transport_offset(skb) + sizeof(struct mld_msg); 109 : : struct mld_msg *mld; 110 : : 111 : 0 : if (!ipv6_mc_may_pull(skb, len)) 112 : : return -EINVAL; 113 : : 114 : : mld = (struct mld_msg *)skb_transport_header(skb); 115 : : 116 : 0 : switch (mld->mld_type) { 117 : : case ICMPV6_MGM_REDUCTION: 118 : : case ICMPV6_MGM_REPORT: 119 : : return 0; 120 : : case ICMPV6_MLD2_REPORT: 121 : 0 : return ipv6_mc_check_mld_reportv2(skb); 122 : : case ICMPV6_MGM_QUERY: 123 : 0 : return ipv6_mc_check_mld_query(skb); 124 : : default: 125 : 0 : return -ENOMSG; 126 : : } 127 : : } 128 : : 129 : 0 : static inline __sum16 ipv6_mc_validate_checksum(struct sk_buff *skb) 130 : : { 131 : 0 : return skb_checksum_validate(skb, IPPROTO_ICMPV6, ip6_compute_pseudo); 132 : : } 133 : : 134 : 0 : int ipv6_mc_check_icmpv6(struct sk_buff *skb) 135 : : { 136 : 0 : unsigned int len = skb_transport_offset(skb) + sizeof(struct icmp6hdr); 137 : : unsigned int transport_len = ipv6_transport_len(skb); 138 : : struct sk_buff *skb_chk; 139 : : 140 : 0 : if (!ipv6_mc_may_pull(skb, len)) 141 : : return -EINVAL; 142 : : 143 : 0 : skb_chk = skb_checksum_trimmed(skb, transport_len, 144 : : ipv6_mc_validate_checksum); 145 : 0 : if (!skb_chk) 146 : : return -EINVAL; 147 : : 148 : 0 : if (skb_chk != skb) 149 : 0 : kfree_skb(skb_chk); 150 : : 151 : : return 0; 152 : : } 153 : : EXPORT_SYMBOL(ipv6_mc_check_icmpv6); 154 : : 155 : : /** 156 : : * ipv6_mc_check_mld - checks whether this is a sane MLD packet 157 : : * @skb: the skb to validate 158 : : * 159 : : * Checks whether an IPv6 packet is a valid MLD packet. If so sets 160 : : * skb transport header accordingly and returns zero. 161 : : * 162 : : * -EINVAL: A broken packet was detected, i.e. it violates some internet 163 : : * standard 164 : : * -ENOMSG: IP header validation succeeded but it is not an MLD packet. 165 : : * -ENOMEM: A memory allocation failure happened. 166 : : * 167 : : * Caller needs to set the skb network header and free any returned skb if it 168 : : * differs from the provided skb. 169 : : */ 170 : 0 : int ipv6_mc_check_mld(struct sk_buff *skb) 171 : : { 172 : : int ret; 173 : : 174 : 0 : ret = ipv6_mc_check_ip6hdr(skb); 175 : 0 : if (ret < 0) 176 : : return ret; 177 : : 178 : 0 : ret = ipv6_mc_check_exthdrs(skb); 179 : 0 : if (ret < 0) 180 : : return ret; 181 : : 182 : 0 : ret = ipv6_mc_check_icmpv6(skb); 183 : 0 : if (ret < 0) 184 : : return ret; 185 : : 186 : 0 : return ipv6_mc_check_mld_msg(skb); 187 : : } 188 : : EXPORT_SYMBOL(ipv6_mc_check_mld);