xref: /xnu-10002.1.13/bsd/netinet6/ah_output.c (revision 1031c584a5e37aff177559b9f69dbd3c8c3fd30a)
1 /*
2  * Copyright (c) 2008-2023 Apple Inc. All rights reserved.
3  *
4  * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5  *
6  * This file contains Original Code and/or Modifications of Original Code
7  * as defined in and that are subject to the Apple Public Source License
8  * Version 2.0 (the 'License'). You may not use this file except in
9  * compliance with the License. The rights granted to you under the License
10  * may not be used to create, or enable the creation or redistribution of,
11  * unlawful or unlicensed copies of an Apple operating system, or to
12  * circumvent, violate, or enable the circumvention or violation of, any
13  * terms of an Apple operating system software license agreement.
14  *
15  * Please obtain a copy of the License at
16  * http://www.opensource.apple.com/apsl/ and read it before using this file.
17  *
18  * The Original Code and all software distributed under the License are
19  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23  * Please see the License for the specific language governing rights and
24  * limitations under the License.
25  *
26  * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27  */
28 
29 /*	$FreeBSD: src/sys/netinet6/ah_output.c,v 1.1.2.3 2001/07/03 11:01:49 ume Exp $	*/
30 /*	$KAME: ah_output.c,v 1.30 2001/02/21 00:50:53 itojun Exp $	*/
31 
32 /*
33  * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
34  * All rights reserved.
35  *
36  * Redistribution and use in source and binary forms, with or without
37  * modification, are permitted provided that the following conditions
38  * are met:
39  * 1. Redistributions of source code must retain the above copyright
40  *    notice, this list of conditions and the following disclaimer.
41  * 2. Redistributions in binary form must reproduce the above copyright
42  *    notice, this list of conditions and the following disclaimer in the
43  *    documentation and/or other materials provided with the distribution.
44  * 3. Neither the name of the project nor the names of its contributors
45  *    may be used to endorse or promote products derived from this software
46  *    without specific prior written permission.
47  *
48  * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
49  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
50  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
51  * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
52  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
53  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
54  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
55  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
56  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
57  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
58  * SUCH DAMAGE.
59  */
60 
61 /*
62  * RFC1826/2402 authentication header.
63  */
64 
65 #define _IP_VHL
66 
67 #include <sys/param.h>
68 #include <sys/systm.h>
69 #include <sys/malloc.h>
70 #include <sys/mbuf.h>
71 #include <sys/domain.h>
72 #include <sys/protosw.h>
73 #include <sys/socket.h>
74 #include <sys/socketvar.h>
75 #include <sys/errno.h>
76 #include <sys/time.h>
77 #include <sys/kernel.h>
78 #include <sys/syslog.h>
79 
80 #include <net/if.h>
81 #include <net/route.h>
82 
83 #include <netinet/in.h>
84 
85 #include <netinet/in_systm.h>
86 #include <netinet/ip.h>
87 #include <netinet/in_var.h>
88 
89 #include <netinet/ip6.h>
90 #include <netinet6/ip6_var.h>
91 #include <netinet/icmp6.h>
92 
93 #include <netinet6/ipsec.h>
94 #include <netinet6/ipsec6.h>
95 #include <netinet6/ah.h>
96 #include <netinet6/ah6.h>
97 #include <netkey/key.h>
98 #include <netkey/keydb.h>
99 
100 #include <net/net_osdep.h>
101 
102 #if INET
103 static struct in_addr *ah4_finaldst(struct mbuf *);
104 
105 static LCK_GRP_DECLARE(sadb_stat_mutex_grp, "sadb_stat");
106 static LCK_MTX_DECLARE(sadb_stat_mutex, &sadb_stat_mutex_grp);
107 #endif
108 
109 /*
110  * compute AH header size.
111  * transport mode only.  for tunnel mode, we should implement
112  * virtual interface, and control MTU/MSS by the interface MTU.
113  */
114 size_t
ah_hdrsiz(struct ipsecrequest * isr)115 ah_hdrsiz(struct ipsecrequest *isr)
116 {
117 	/* sanity check */
118 	if (isr == NULL) {
119 		panic("ah_hdrsiz: NULL was passed.");
120 	}
121 
122 	if (isr->saidx.proto != IPPROTO_AH) {
123 		panic("unsupported mode passed to ah_hdrsiz");
124 	}
125 
126 #if 0
127 	{
128 		lck_mtx_lock(sadb_mutex);
129 		const struct ah_algorithm *algo;
130 		size_t hdrsiz;
131 
132 		/*%%%%% this needs to change - no sav in ipsecrequest any more */
133 		if (isr->sav == NULL) {
134 			goto estimate;
135 		}
136 		if (isr->sav->state != SADB_SASTATE_MATURE
137 		    && isr->sav->state != SADB_SASTATE_DYING) {
138 			goto estimate;
139 		}
140 
141 		/* we need transport mode AH. */
142 		algo = ah_algorithm_lookup(isr->sav->alg_auth);
143 		if (!algo) {
144 			goto estimate;
145 		}
146 
147 		/*
148 		 * XXX
149 		 * right now we don't calcurate the padding size.  simply
150 		 * treat the padding size as constant, for simplicity.
151 		 *
152 		 * XXX variable size padding support
153 		 */
154 		hdrsiz = (((*algo->sumsiz)(isr->sav) + 3) & ~(4 - 1));
155 		if (isr->sav->flags & SADB_X_EXT_OLD) {
156 			hdrsiz += sizeof(struct ah);
157 		} else {
158 			hdrsiz += sizeof(struct newah);
159 		}
160 
161 		lck_mtx_unlock(sadb_mutex);
162 		return hdrsiz;
163 	}
164 
165 estimate:
166 #endif
167 
168 	//lck_mtx_unlock(sadb_mutex);
169 	/* ASSUMING:
170 	 *	sizeof(struct newah) > sizeof(struct ah).
171 	 *	16 = (16 + 3) & ~(4 - 1).
172 	 */
173 	return sizeof(struct newah) + 16;
174 }
175 
176 #if INET
177 /*
178  * Modify the packet so that it includes the authentication data.
179  * The mbuf passed must start with IPv4 header.
180  *
181  * assumes that the first mbuf contains IPv4 header + option only.
182  * the function does not modify m.
183  */
184 int
ah4_output(struct mbuf * m,struct secasvar * sav)185 ah4_output(struct mbuf *m, struct secasvar *sav)
186 {
187 	const struct ah_algorithm *algo;
188 	u_int32_t spi;
189 	u_char *ahdrpos;
190 	u_char *ahsumpos = NULL;
191 	size_t hlen = 0;        /*IP header+option in bytes*/
192 	size_t plen = 0;        /*AH payload size in bytes*/
193 	size_t ahlen = 0;       /*plen + sizeof(ah)*/
194 	struct ip *ip;
195 	struct in_addr dst = { .s_addr = 0 };
196 	struct in_addr *finaldst;
197 	int error;
198 
199 	/* sanity checks */
200 	if ((sav->flags & SADB_X_EXT_OLD) == 0 && sav->replay[0] == NULL) {
201 		ip = mtod(m, struct ip *);
202 		ipseclog((LOG_DEBUG, "ah4_output: internal error: "
203 		    "sav->replay is null: %x->%x, SPI=%u\n",
204 		    (u_int32_t)ntohl(ip->ip_src.s_addr),
205 		    (u_int32_t)ntohl(ip->ip_dst.s_addr),
206 		    (u_int32_t)ntohl(sav->spi)));
207 		IPSEC_STAT_INCREMENT(ipsecstat.out_inval);
208 		m_freem(m);
209 		return EINVAL;
210 	}
211 
212 	algo = ah_algorithm_lookup(sav->alg_auth);
213 	if (!algo) {
214 		ipseclog((LOG_ERR, "ah4_output: unsupported algorithm: "
215 		    "SPI=%u\n", (u_int32_t)ntohl(sav->spi)));
216 		IPSEC_STAT_INCREMENT(ipsecstat.out_inval);
217 		m_freem(m);
218 		return EINVAL;
219 	}
220 	spi = sav->spi;
221 
222 	/*
223 	 * determine the size to grow.
224 	 */
225 	if (sav->flags & SADB_X_EXT_OLD) {
226 		/* RFC 1826 */
227 		plen = ((*algo->sumsiz)(sav) + 3) & ~(4 - 1); /*XXX pad to 8byte?*/
228 		ahlen = plen + sizeof(struct ah);
229 	} else {
230 		/* RFC 2402 */
231 		plen = ((*algo->sumsiz)(sav) + 3) & ~(4 - 1); /*XXX pad to 8byte?*/
232 		ahlen = plen + sizeof(struct newah);
233 	}
234 
235 	VERIFY(ahlen <= UINT16_MAX);
236 
237 	/*
238 	 * grow the mbuf to accomodate AH.
239 	 */
240 	ip = mtod(m, struct ip *);
241 #ifdef _IP_VHL
242 	hlen = IP_VHL_HL(ip->ip_vhl) << 2;
243 #else
244 	hlen = ip->ip_hl << 2;
245 #endif
246 
247 	if (m->m_len != hlen) {
248 		panic("ah4_output: assumption failed (first mbuf length)");
249 	}
250 	if (M_LEADINGSPACE(m->m_next) < ahlen) {
251 		struct mbuf *n;
252 		MGET(n, M_DONTWAIT, MT_DATA);
253 		if (!n) {
254 			ipseclog((LOG_DEBUG, "ENOBUFS in ah4_output %d\n",
255 			    __LINE__));
256 			m_freem(m);
257 			return ENOBUFS;
258 		}
259 		n->m_len = (int32_t)ahlen;
260 		n->m_next = m->m_next;
261 		m->m_next = n;
262 		m->m_pkthdr.len += ahlen;
263 		ahdrpos = mtod(n, u_char *);
264 	} else {
265 		m->m_next->m_len += ahlen;
266 		m->m_next->m_data -= ahlen;
267 		m->m_pkthdr.len += ahlen;
268 		ahdrpos = mtod(m->m_next, u_char *);
269 	}
270 
271 	ip = mtod(m, struct ip *);      /*just to be sure*/
272 
273 	/*
274 	 * initialize AH.
275 	 */
276 	if (sav->flags & SADB_X_EXT_OLD) {
277 		struct ah *ahdr;
278 
279 		VERIFY((plen >> 2) <= UINT8_MAX);
280 		ahdr = (struct ah *)(void *)ahdrpos;
281 		ahsumpos = (u_char *)(ahdr + 1);
282 		ahdr->ah_len = (u_int8_t)(plen >> 2);
283 		ahdr->ah_nxt = ip->ip_p;
284 		ahdr->ah_reserve = htons(0);
285 		ahdr->ah_spi = spi;
286 		bzero(ahdr + 1, plen);
287 	} else {
288 		struct newah *ahdr;
289 
290 		VERIFY(((plen >> 2) + 1) <= UINT8_MAX);
291 		ahdr = (struct newah *)(void *)ahdrpos;
292 		ahsumpos = (u_char *)(ahdr + 1);
293 		ahdr->ah_len = (u_int8_t)((plen >> 2) + 1); /* plus one for seq# */
294 		ahdr->ah_nxt = ip->ip_p;
295 		ahdr->ah_reserve = htons(0);
296 		ahdr->ah_spi = spi;
297 		if (sav->replay[0]->count == ~0) {
298 			if ((sav->flags & SADB_X_EXT_CYCSEQ) == 0) {
299 				/* XXX Is it noisy ? */
300 				ipseclog((LOG_WARNING,
301 				    "replay counter overflowed. %s\n",
302 				    ipsec_logsastr(sav)));
303 				IPSEC_STAT_INCREMENT(ipsecstat.out_inval);
304 				m_freem(m);
305 				return EINVAL;
306 			}
307 		}
308 		lck_mtx_lock(sadb_mutex);
309 		sav->replay[0]->count++;
310 		lck_mtx_unlock(sadb_mutex);
311 		/*
312 		 * XXX sequence number must not be cycled, if the SA is
313 		 * installed by IKE daemon.
314 		 */
315 		ahdr->ah_seq = htonl(sav->replay[0]->count);
316 		bzero(ahdr + 1, plen);
317 	}
318 
319 	/*
320 	 * modify IPv4 header.
321 	 */
322 	ip->ip_p = IPPROTO_AH;
323 	if (ahlen < (IP_MAXPACKET - ntohs(ip->ip_len))) {
324 		ip->ip_len = htons(ntohs(ip->ip_len) + (u_int16_t)ahlen);
325 	} else {
326 		ipseclog((LOG_ERR, "IPv4 AH output: size exceeds limit\n"));
327 		IPSEC_STAT_INCREMENT(ipsecstat.out_inval);
328 		m_freem(m);
329 		return EMSGSIZE;
330 	}
331 
332 	/*
333 	 * If there is source routing option, update destination field in
334 	 * the IPv4 header to the final destination.
335 	 * Note that we do not need to update source routing option itself
336 	 * (as done in IPv4 AH processing -- see ip6_output()), since
337 	 * source routing option is not part of the ICV computation.
338 	 */
339 	finaldst = ah4_finaldst(m);
340 	if (finaldst) {
341 		dst.s_addr = ip->ip_dst.s_addr;
342 		ip->ip_dst.s_addr = finaldst->s_addr;
343 	}
344 
345 	/*
346 	 * calcurate the checksum, based on security association
347 	 * and the algorithm specified.
348 	 */
349 	error = ah4_calccksum(m, (caddr_t)ahsumpos, plen, algo, sav);
350 	if (error) {
351 		ipseclog((LOG_ERR,
352 		    "error after ah4_calccksum, called from ah4_output"));
353 		m_freem(m);
354 		m = NULL;
355 		IPSEC_STAT_INCREMENT(ipsecstat.out_inval);
356 		return error;
357 	}
358 
359 	if (finaldst) {
360 		ip = mtod(m, struct ip *);      /*just to make sure*/
361 		ip->ip_dst.s_addr = dst.s_addr;
362 	}
363 	lck_mtx_lock(&sadb_stat_mutex);
364 	ipsecstat.out_success++;
365 	ipsecstat.out_ahhist[sav->alg_auth]++;
366 	lck_mtx_unlock(&sadb_stat_mutex);
367 	key_sa_recordxfer(sav, m->m_pkthdr.len);
368 
369 	return 0;
370 }
371 #endif
372 
373 /* Calculate AH length */
374 size_t
ah_hdrlen(struct secasvar * sav)375 ah_hdrlen(struct secasvar *sav)
376 {
377 	const struct ah_algorithm *algo;
378 	size_t plen, ahlen;
379 
380 	algo = ah_algorithm_lookup(sav->alg_auth);
381 	if (!algo) {
382 		return 0;
383 	}
384 	if (sav->flags & SADB_X_EXT_OLD) {
385 		/* RFC 1826 */
386 		plen = ((*algo->sumsiz)(sav) + 3) & ~(4 - 1);   /*XXX pad to 8byte?*/
387 		ahlen = plen + sizeof(struct ah);
388 	} else {
389 		/* RFC 2402 */
390 		plen = ((*algo->sumsiz)(sav) + 3) & ~(4 - 1);   /*XXX pad to 8byte?*/
391 		ahlen = plen + sizeof(struct newah);
392 	}
393 
394 	return ahlen;
395 }
396 
397 /*
398  * Fill in the Authentication Header and calculate checksum.
399  */
400 int
ah6_output(struct mbuf * m,u_char * nexthdrp,struct mbuf * md,struct secasvar * sav)401 ah6_output(struct mbuf *m, u_char *nexthdrp, struct mbuf *md,
402     struct secasvar *sav)
403 {
404 	struct mbuf *mprev;
405 	struct mbuf *mah;
406 	const struct ah_algorithm *algo;
407 	u_int32_t spi;
408 	u_char *ahsumpos = NULL;
409 	size_t plen;    /*AH payload size in bytes*/
410 	int error = 0;
411 	size_t ahlen;
412 	struct ip6_hdr *ip6;
413 
414 	if (m->m_len < sizeof(struct ip6_hdr)) {
415 		ipseclog((LOG_DEBUG, "ah6_output: first mbuf too short\n"));
416 		m_freem(m);
417 		return EINVAL;
418 	}
419 
420 	ahlen = ah_hdrlen(sav);
421 	if (ahlen == 0) {
422 		return 0;
423 	}
424 
425 	VERIFY(ahlen <= UINT16_MAX);
426 
427 	for (mprev = m; mprev && mprev->m_next != md; mprev = mprev->m_next) {
428 		;
429 	}
430 	if (!mprev || mprev->m_next != md) {
431 		ipseclog((LOG_DEBUG, "ah6_output: md is not in chain\n"));
432 		m_freem(m);
433 		return EINVAL;
434 	}
435 
436 	MGET(mah, M_DONTWAIT, MT_DATA);
437 	if (!mah) {
438 		m_freem(m);
439 		return ENOBUFS;
440 	}
441 	if (ahlen > MLEN) {
442 		MCLGET(mah, M_DONTWAIT);
443 		if ((mah->m_flags & M_EXT) == 0) {
444 			m_free(mah);
445 			m_freem(m);
446 			return ENOBUFS;
447 		}
448 	}
449 	mah->m_len = (int32_t)ahlen;
450 	mah->m_next = md;
451 	mprev->m_next = mah;
452 	m->m_pkthdr.len += ahlen;
453 
454 	/* fix plen */
455 	if (m->m_pkthdr.len - sizeof(struct ip6_hdr) > IPV6_MAXPACKET) {
456 		ipseclog((LOG_ERR,
457 		    "ip6_output: AH with IPv6 jumbogram is not supported\n"));
458 		m_freem(m);
459 		return EINVAL;
460 	}
461 
462 	ip6 = mtod(m, struct ip6_hdr *);
463 	ip6->ip6_plen = htons((u_int16_t)(m->m_pkthdr.len - sizeof(struct ip6_hdr)));
464 
465 	if ((sav->flags & SADB_X_EXT_OLD) == 0 && sav->replay[0] == NULL) {
466 		ipseclog((LOG_DEBUG, "ah6_output: internal error: "
467 		    "sav->replay is null: SPI=%u\n",
468 		    (u_int32_t)ntohl(sav->spi)));
469 		IPSEC_STAT_INCREMENT(ipsec6stat.out_inval);
470 		m_freem(m);
471 		return EINVAL;
472 	}
473 
474 	algo = ah_algorithm_lookup(sav->alg_auth);
475 	if (!algo) {
476 		ipseclog((LOG_ERR, "ah6_output: unsupported algorithm: "
477 		    "SPI=%u\n", (u_int32_t)ntohl(sav->spi)));
478 		IPSEC_STAT_INCREMENT(ipsec6stat.out_inval);
479 		m_freem(m);
480 		return EINVAL;
481 	}
482 	spi = sav->spi;
483 
484 	/*
485 	 * initialize AH.
486 	 */
487 	if (sav->flags & SADB_X_EXT_OLD) {
488 		struct ah *ahdr = mtod(mah, struct ah *);
489 
490 		plen = mah->m_len - sizeof(struct ah);
491 		VERIFY((plen >> 2) <= UINT8_MAX);
492 		ahsumpos = (u_char *)(ahdr + 1);
493 		ahdr->ah_nxt = *nexthdrp;
494 		*nexthdrp = IPPROTO_AH;
495 		ahdr->ah_len = (u_int8_t)(plen >> 2);
496 		ahdr->ah_reserve = htons(0);
497 		ahdr->ah_spi = spi;
498 		bzero(ahdr + 1, plen);
499 	} else {
500 		struct newah *ahdr = mtod(mah, struct newah *);
501 
502 		plen = mah->m_len - sizeof(struct newah);
503 		VERIFY(((plen >> 2) + 1) <= UINT8_MAX);
504 		ahsumpos = (u_char *)(ahdr + 1);
505 		ahdr->ah_nxt = *nexthdrp;
506 		*nexthdrp = IPPROTO_AH;
507 		ahdr->ah_len = (u_int8_t)((plen >> 2) + 1); /* plus one for seq# */
508 		ahdr->ah_reserve = htons(0);
509 		ahdr->ah_spi = spi;
510 		if (sav->replay[0]->count == ~0) {
511 			if ((sav->flags & SADB_X_EXT_CYCSEQ) == 0) {
512 				/* XXX Is it noisy ? */
513 				ipseclog((LOG_WARNING,
514 				    "replay counter overflowed. %s\n",
515 				    ipsec_logsastr(sav)));
516 				IPSEC_STAT_INCREMENT(ipsec6stat.out_inval);
517 				m_freem(m);
518 				return EINVAL;
519 			}
520 		}
521 		lck_mtx_lock(sadb_mutex);
522 		sav->replay[0]->count++;
523 		lck_mtx_unlock(sadb_mutex);
524 		/*
525 		 * XXX sequence number must not be cycled, if the SA is
526 		 * installed by IKE daemon.
527 		 */
528 		ahdr->ah_seq = htonl(sav->replay[0]->count);
529 		bzero(ahdr + 1, plen);
530 	}
531 
532 	/*
533 	 * calcurate the checksum, based on security association
534 	 * and the algorithm specified.
535 	 */
536 	error = ah6_calccksum(m, (caddr_t)ahsumpos, plen, algo, sav);
537 	if (error) {
538 		IPSEC_STAT_INCREMENT(ipsec6stat.out_inval);
539 		m_freem(m);
540 	} else {
541 		IPSEC_STAT_INCREMENT(ipsec6stat.out_success);
542 		key_sa_recordxfer(sav, m->m_pkthdr.len);
543 	}
544 	IPSEC_STAT_INCREMENT(ipsec6stat.out_ahhist[sav->alg_auth]);
545 
546 	return error;
547 }
548 
549 /*
550  * Find the final destination if there is loose/strict source routing option.
551  * Returns NULL if there's no source routing options.
552  * Returns NULL on errors too.
553  * Note that this function will return a pointer INTO the given parameter,
554  * struct mbuf *m.
555  * The mbuf must be pulled up toward, at least, ip option part.
556  */
557 static struct in_addr *
ah4_finaldst(struct mbuf * m)558 ah4_finaldst(struct mbuf *m)
559 {
560 	struct ip *ip;
561 	int optlen;
562 	u_char *q;
563 	int i;
564 	int hlen;
565 
566 	if (!m) {
567 		panic("ah4_finaldst: m == NULL");
568 	}
569 	ip = mtod(m, struct ip *);
570 #ifdef _IP_VHL
571 	hlen = IP_VHL_HL(ip->ip_vhl) << 2;
572 #else
573 	hlen = ip->ip_hl << 2;
574 #endif
575 
576 	if (m->m_len < hlen) {
577 		ipseclog((LOG_DEBUG,
578 		    "ah4_finaldst: parameter mbuf wrong (not pulled up)\n"));
579 		return NULL;
580 	}
581 
582 	if (hlen == sizeof(struct ip)) {
583 		return NULL;
584 	}
585 
586 	optlen = hlen - sizeof(struct ip);
587 	if (optlen < 0) {
588 		ipseclog((LOG_DEBUG, "ah4_finaldst: wrong optlen %d\n",
589 		    optlen));
590 		return NULL;
591 	}
592 
593 	q = (u_char *)(ip + 1);
594 	i = 0;
595 	while (i < optlen) {
596 		if (i + IPOPT_OPTVAL >= optlen) {
597 			return NULL;
598 		}
599 		if (q[i + IPOPT_OPTVAL] == IPOPT_EOL ||
600 		    q[i + IPOPT_OPTVAL] == IPOPT_NOP ||
601 		    i + IPOPT_OLEN < optlen) {
602 			;
603 		} else {
604 			return NULL;
605 		}
606 
607 		switch (q[i + IPOPT_OPTVAL]) {
608 		case IPOPT_EOL:
609 			i = optlen;     /* bye */
610 			break;
611 		case IPOPT_NOP:
612 			i++;
613 			break;
614 		case IPOPT_LSRR:
615 		case IPOPT_SSRR:
616 			if (q[i + IPOPT_OLEN] < 2 + sizeof(struct in_addr) ||
617 			    optlen - i < q[i + IPOPT_OLEN]) {
618 				ipseclog((LOG_ERR,
619 				    "ip_finaldst: invalid IP option "
620 				    "(code=%02x len=%02x)\n",
621 				    q[i + IPOPT_OPTVAL], q[i + IPOPT_OLEN]));
622 				return NULL;
623 			}
624 			i += q[i + IPOPT_OLEN] - sizeof(struct in_addr);
625 			return (struct in_addr *)(void *)(q + i);
626 		default:
627 			if (q[i + IPOPT_OLEN] < 2 ||
628 			    optlen - i < q[i + IPOPT_OLEN]) {
629 				ipseclog((LOG_ERR,
630 				    "ip_finaldst: invalid IP option "
631 				    "(code=%02x len=%02x)\n",
632 				    q[i + IPOPT_OPTVAL], q[i + IPOPT_OLEN]));
633 				return NULL;
634 			}
635 			i += q[i + IPOPT_OLEN];
636 			break;
637 		}
638 	}
639 	return NULL;
640 }
641