1 // SPDX-License-Identifier: GPL-2.0-only 2 3 #include <linux/skbuff.h> 4 #include <net/addrconf.h> 5 #include <net/ip6_checksum.h> 6 #include <net/ipv6.h> 7 #include <net/ndisc.h> 8 9 static int ndisc_check_ip6hdr(struct sk_buff *skb) 10 { 11 const struct ipv6hdr *ip6h; 12 unsigned int offset, len; 13 14 offset = skb_network_offset(skb) + sizeof(*ip6h); 15 if (!pskb_may_pull(skb, offset)) 16 return -EINVAL; 17 18 ip6h = ipv6_hdr(skb); 19 20 if (ip6h->version != 6) 21 return -EINVAL; 22 23 if (ip6h->nexthdr != IPPROTO_ICMPV6) 24 return -ENOMSG; 25 26 /* RFC 4861 7.1.1 / 7.1.2: must not have been forwarded by a router */ 27 if (ip6h->hop_limit != 255) 28 return -EINVAL; 29 30 len = offset + ntohs(ip6h->payload_len); 31 if (skb->len < len || len <= offset) 32 return -EINVAL; 33 34 skb_set_transport_header(skb, offset); 35 36 return 0; 37 } 38 39 static __sum16 ndisc_validate_checksum(struct sk_buff *skb) 40 { 41 return skb_checksum_validate(skb, IPPROTO_ICMPV6, ip6_compute_pseudo); 42 } 43 44 static int ndisc_check_icmpv6(struct sk_buff *skb) 45 { 46 unsigned int len = skb_transport_offset(skb) + sizeof(struct icmp6hdr); 47 unsigned int transport_len = ipv6_transport_len(skb); 48 struct sk_buff *skb_chk; 49 struct icmp6hdr *hdr; 50 51 if (!pskb_may_pull(skb, len)) 52 return -EINVAL; 53 54 /* RFC 4861 7.1.1 / 7.1.2: the ICMPv6 checksum must be valid */ 55 skb_chk = skb_checksum_trimmed(skb, transport_len, 56 ndisc_validate_checksum); 57 if (!skb_chk) 58 return -EINVAL; 59 60 if (skb_chk != skb) 61 kfree_skb(skb_chk); 62 63 /* RFC 4861 7.1.1 / 7.1.2: Code must be 0 */ 64 hdr = (struct icmp6hdr *)skb_transport_header(skb); 65 if (hdr->icmp6_code != 0) 66 return -EINVAL; 67 68 return 0; 69 } 70 71 static int ndisc_check_options(struct sk_buff *skb, unsigned int opts_len, 72 bool reject_slla) 73 { 74 unsigned int offset = skb_transport_offset(skb) + sizeof(struct nd_msg); 75 struct nd_opt_hdr *opt, _opt; 76 77 while (opts_len > 0) { 78 if (opts_len < sizeof(*opt)) 79 return -EINVAL; 80 81 opt = skb_header_pointer(skb, offset, sizeof(_opt), &_opt); 82 if (!opt) 83 return -EINVAL; 84 85 /* RFC 4861 7.1.1 / 7.1.2: all option lengths must be > 0 */ 86 if (!opt->nd_opt_len) 87 return -EINVAL; 88 89 /* RFC 4861 7.1.1: DAD NS must not contain a source link-layer 90 * address option 91 */ 92 if (reject_slla && opt->nd_opt_type == ND_OPT_SOURCE_LL_ADDR) 93 return -EINVAL; 94 95 if (opt->nd_opt_len * 8 > opts_len) 96 return -EINVAL; 97 98 offset += opt->nd_opt_len * 8; 99 opts_len -= opt->nd_opt_len * 8; 100 } 101 102 return 0; 103 } 104 105 static int ndisc_check_nd_msg(struct sk_buff *skb) 106 { 107 unsigned int len = skb_transport_offset(skb) + sizeof(struct nd_msg); 108 unsigned int transport_len = ipv6_transport_len(skb); 109 bool reject_slla = false; 110 const struct nd_msg *msg; 111 112 if (!pskb_may_pull(skb, len)) 113 return -EINVAL; 114 115 /* RFC 4861 7.1.1 / 7.1.2: ICMP length is at least sizeof(nd_msg) */ 116 if (transport_len < sizeof(struct nd_msg)) 117 return -EINVAL; 118 119 msg = (struct nd_msg *)skb_transport_header(skb); 120 121 /* RFC 4861 7.1.1 / 7.1.2: Target Address must not be a 122 * multicast address 123 */ 124 if (ipv6_addr_is_multicast(&msg->target)) 125 return -EINVAL; 126 127 switch (msg->icmph.icmp6_type) { 128 case NDISC_NEIGHBOUR_SOLICITATION: 129 if (ipv6_addr_any(&ipv6_hdr(skb)->saddr)) { 130 /* RFC 4861 7.1.1: DAD NS destination must be a 131 * solicited-node multicast address 132 */ 133 if (!ipv6_addr_is_solict_mult(&ipv6_hdr(skb)->daddr)) 134 return -EINVAL; 135 /* RFC 4861 7.1.1: DAD NS must not contain a source 136 * link-layer address option 137 */ 138 reject_slla = true; 139 } 140 break; 141 case NDISC_NEIGHBOUR_ADVERTISEMENT: 142 /* RFC 4861 7.1.2: Solicited flag must be 0 for 143 * multicast destinations 144 */ 145 if (ipv6_addr_is_multicast(&ipv6_hdr(skb)->daddr) && 146 msg->icmph.icmp6_solicited) 147 return -EINVAL; 148 break; 149 default: 150 return -ENODATA; 151 } 152 153 return ndisc_check_options(skb, transport_len - sizeof(struct nd_msg), 154 reject_slla); 155 } 156 157 /** 158 * ndisc_check_ns_na - validate an NS/NA packet and set its transport header 159 * @skb: the skb to validate 160 * 161 * Validates an IPv6 packet for compliance with RFC 4861 sections 7.1.1 162 * (Neighbor Solicitation) and 7.1.2 (Neighbor Advertisement). If valid, 163 * sets the skb transport header. 164 * 165 * Caller needs to set the skb network header. 166 * 167 * Return: 168 * * 0 - valid NS/NA; the skb transport header has been set. 169 * * -EINVAL - a broken packet was detected, i.e. it violates some 170 * internet standard. 171 * * -ENOMSG - IP header validation succeeded but it is not an ICMPv6 172 * packet. 173 * * -ENODATA - IP+ICMPv6 header validation succeeded but it is not a 174 * Neighbor Solicitation or Neighbor Advertisement. 175 */ 176 int ndisc_check_ns_na(struct sk_buff *skb) 177 { 178 int ret; 179 180 ret = ndisc_check_ip6hdr(skb); 181 if (ret < 0) 182 return ret; 183 184 ret = ndisc_check_icmpv6(skb); 185 if (ret < 0) 186 return ret; 187 188 return ndisc_check_nd_msg(skb); 189 } 190 EXPORT_SYMBOL_GPL(ndisc_check_ns_na); 191