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