]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - lib/libc/net/ip6opt.c
Fix multiple denial of service in ntpd.
[FreeBSD/FreeBSD.git] / lib / libc / net / ip6opt.c
1 /*      $KAME: ip6opt.c,v 1.13 2003/06/06 10:08:20 suz Exp $    */
2
3 /*
4  * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
5  * All rights reserved.
6  * 
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. Neither the name of the project nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  * 
19  * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  */
31
32 #include <sys/cdefs.h>
33 __FBSDID("$FreeBSD$");
34
35 #include <sys/param.h>
36 #include <sys/socket.h>
37
38 #include <netinet/in.h>
39 #include <netinet/ip6.h>
40
41 #include <string.h>
42 #include <stdio.h>
43
44 static int ip6optlen(u_int8_t *opt, u_int8_t *lim);
45 static void inet6_insert_padopt(u_char *p, int len);
46
47 #ifndef IPV6_2292HOPOPTS
48 #define IPV6_2292HOPOPTS        22
49 #endif
50 #ifndef IPV6_2292DSTOPTS
51 #define IPV6_2292DSTOPTS        23
52 #endif
53
54 #define is_ipv6_hopopts(x)      \
55         ((x) == IPV6_HOPOPTS || (x) == IPV6_2292HOPOPTS)
56 #define is_ipv6_dstopts(x)      \
57         ((x) == IPV6_DSTOPTS || (x) == IPV6_2292DSTOPTS)
58
59 /*
60  * This function returns the number of bytes required to hold an option
61  * when it is stored as ancillary data, including the cmsghdr structure
62  * at the beginning, and any padding at the end (to make its size a
63  * multiple of 8 bytes).  The argument is the size of the structure
64  * defining the option, which must include any pad bytes at the
65  * beginning (the value y in the alignment term "xn + y"), the type
66  * byte, the length byte, and the option data.
67  */
68 int
69 inet6_option_space(int nbytes)
70 {
71         nbytes += 2;    /* we need space for nxt-hdr and length fields */
72         return(CMSG_SPACE((nbytes + 7) & ~7));
73 }
74
75 /*
76  * This function is called once per ancillary data object that will
77  * contain either Hop-by-Hop or Destination options.  It returns 0 on
78  * success or -1 on an error.
79  */
80 int
81 inet6_option_init(void *bp, struct cmsghdr **cmsgp, int type)
82 {
83         struct cmsghdr *ch = (struct cmsghdr *)bp;
84
85         /* argument validation */
86         if (!is_ipv6_hopopts(type) && !is_ipv6_dstopts(type))
87                 return(-1);
88
89         ch->cmsg_level = IPPROTO_IPV6;
90         ch->cmsg_type = type;
91         ch->cmsg_len = CMSG_LEN(0);
92
93         *cmsgp = ch;
94         return(0);
95 }
96
97 /*
98  * This function appends a Hop-by-Hop option or a Destination option
99  * into an ancillary data object that has been initialized by
100  * inet6_option_init().  This function returns 0 if it succeeds or -1 on
101  * an error.
102  * multx is the value x in the alignment term "xn + y" described
103  * earlier.  It must have a value of 1, 2, 4, or 8.
104  * plusy is the value y in the alignment term "xn + y" described
105  * earlier.  It must have a value between 0 and 7, inclusive.
106  */
107 int
108 inet6_option_append(struct cmsghdr *cmsg, const u_int8_t *typep, int multx,
109     int plusy)
110 {
111         int padlen, optlen, off;
112         u_char *bp = (u_char *)cmsg + cmsg->cmsg_len;
113         struct ip6_ext *eh = (struct ip6_ext *)CMSG_DATA(cmsg);
114
115         /* argument validation */
116         if (multx != 1 && multx != 2 && multx != 4 && multx != 8)
117                 return(-1);
118         if (plusy < 0 || plusy > 7)
119                 return(-1);
120
121         /*
122          * If this is the first option, allocate space for the
123          * first 2 bytes(for next header and length fields) of
124          * the option header.
125          */
126         if (bp == (u_char *)eh) {
127                 bp += 2;
128                 cmsg->cmsg_len += 2;
129         }
130
131         /* calculate pad length before the option. */
132         off = bp - (u_char *)eh;
133         padlen = roundup2(off % multx, multx) - (off % multx);
134         padlen += plusy;
135         padlen %= multx;        /* keep the pad as short as possible */
136         /* insert padding */
137         inet6_insert_padopt(bp, padlen);
138         cmsg->cmsg_len += padlen;
139         bp += padlen;
140
141         /* copy the option */
142         if (typep[0] == IP6OPT_PAD1)
143                 optlen = 1;
144         else
145                 optlen = typep[1] + 2;
146         memcpy(bp, typep, optlen);
147         bp += optlen;
148         cmsg->cmsg_len += optlen;
149
150         /* calculate pad length after the option and insert the padding */
151         off = bp - (u_char *)eh;
152         padlen = ((off + 7) & ~7) - off;
153         inet6_insert_padopt(bp, padlen);
154         bp += padlen;
155         cmsg->cmsg_len += padlen;
156
157         /* update the length field of the ip6 option header */
158         eh->ip6e_len = ((bp - (u_char *)eh) >> 3) - 1;
159
160         return(0);
161 }
162
163 /*
164  * This function appends a Hop-by-Hop option or a Destination option
165  * into an ancillary data object that has been initialized by
166  * inet6_option_init().  This function returns a pointer to the 8-bit
167  * option type field that starts the option on success, or NULL on an
168  * error.
169  * The difference between this function and inet6_option_append() is
170  * that the latter copies the contents of a previously built option into
171  * the ancillary data object while the current function returns a
172  * pointer to the space in the data object where the option's TLV must
173  * then be built by the caller.
174  * 
175  */
176 u_int8_t *
177 inet6_option_alloc(struct cmsghdr *cmsg, int datalen, int multx, int plusy)
178 {
179         int padlen, off;
180         u_int8_t *bp = (u_char *)cmsg + cmsg->cmsg_len;
181         u_int8_t *retval;
182         struct ip6_ext *eh = (struct ip6_ext *)CMSG_DATA(cmsg);
183
184         /* argument validation */
185         if (multx != 1 && multx != 2 && multx != 4 && multx != 8)
186                 return(NULL);
187         if (plusy < 0 || plusy > 7)
188                 return(NULL);
189
190         /*
191          * If this is the first option, allocate space for the
192          * first 2 bytes(for next header and length fields) of
193          * the option header.
194          */
195         if (bp == (u_char *)eh) {
196                 bp += 2;
197                 cmsg->cmsg_len += 2;
198         }
199
200         /* calculate pad length before the option. */
201         off = bp - (u_char *)eh;
202         padlen = roundup2(off % multx, multx) - (off % multx);
203         padlen += plusy;
204         padlen %= multx;        /* keep the pad as short as possible */
205         /* insert padding */
206         inet6_insert_padopt(bp, padlen);
207         cmsg->cmsg_len += padlen;
208         bp += padlen;
209
210         /* keep space to store specified length of data */
211         retval = bp;
212         bp += datalen;
213         cmsg->cmsg_len += datalen;
214
215         /* calculate pad length after the option and insert the padding */
216         off = bp - (u_char *)eh;
217         padlen = ((off + 7) & ~7) - off;
218         inet6_insert_padopt(bp, padlen);
219         bp += padlen;
220         cmsg->cmsg_len += padlen;
221
222         /* update the length field of the ip6 option header */
223         eh->ip6e_len = ((bp - (u_char *)eh) >> 3) - 1;
224
225         return(retval);
226 }
227
228 /*
229  * This function processes the next Hop-by-Hop option or Destination
230  * option in an ancillary data object.  If another option remains to be
231  * processed, the return value of the function is 0 and *tptrp points to
232  * the 8-bit option type field (which is followed by the 8-bit option
233  * data length, followed by the option data).  If no more options remain
234  * to be processed, the return value is -1 and *tptrp is NULL.  If an
235  * error occurs, the return value is -1 and *tptrp is not NULL.
236  * (RFC 2292, 6.3.5)
237  */
238 int
239 inet6_option_next(const struct cmsghdr *cmsg, u_int8_t **tptrp)
240 {
241         struct ip6_ext *ip6e;
242         int hdrlen, optlen;
243         u_int8_t *lim;
244
245         if (cmsg->cmsg_level != IPPROTO_IPV6 ||
246             (!is_ipv6_hopopts(cmsg->cmsg_type) &&
247              !is_ipv6_dstopts(cmsg->cmsg_type)))
248                 return(-1);
249
250         /* message length validation */
251         if (cmsg->cmsg_len < CMSG_SPACE(sizeof(struct ip6_ext)))
252                 return(-1);
253         ip6e = (struct ip6_ext *)CMSG_DATA(cmsg);
254         hdrlen = (ip6e->ip6e_len + 1) << 3;
255         if (cmsg->cmsg_len < CMSG_SPACE(hdrlen))
256                 return(-1);
257
258         /*
259          * If the caller does not specify the starting point,
260          * simply return the 1st option.
261          * Otherwise, search the option list for the next option.
262          */
263         lim = (u_int8_t *)ip6e + hdrlen;
264         if (*tptrp == NULL)
265                 *tptrp = (u_int8_t *)(ip6e + 1);
266         else {
267                 if ((optlen = ip6optlen(*tptrp, lim)) == 0)
268                         return(-1);
269
270                 *tptrp = *tptrp + optlen;
271         }
272         if (*tptrp >= lim) {    /* there is no option */
273                 *tptrp = NULL;
274                 return(-1);
275         }
276         /*
277          * Finally, checks if the next option is safely stored in the
278          * cmsg data.
279          */
280         if (ip6optlen(*tptrp, lim) == 0)
281                 return(-1);
282         else
283                 return(0);
284 }
285
286 /*
287  * This function is similar to the inet6_option_next() function,
288  * except this function lets the caller specify the option type to be
289  * searched for, instead of always returning the next option in the
290  * ancillary data object.
291  * Note: RFC 2292 says the type of tptrp is u_int8_t *, but we think
292  *       it's a typo. The variable should be type of u_int8_t **.
293  */
294 int
295 inet6_option_find(const struct cmsghdr *cmsg, u_int8_t **tptrp, int type)
296 {
297         struct ip6_ext *ip6e;
298         int hdrlen, optlen;
299         u_int8_t *optp, *lim;
300
301         if (cmsg->cmsg_level != IPPROTO_IPV6 ||
302             (!is_ipv6_hopopts(cmsg->cmsg_type) &&
303              !is_ipv6_dstopts(cmsg->cmsg_type)))
304                 return(-1);
305
306         /* message length validation */
307         if (cmsg->cmsg_len < CMSG_SPACE(sizeof(struct ip6_ext)))
308                 return(-1);
309         ip6e = (struct ip6_ext *)CMSG_DATA(cmsg);
310         hdrlen = (ip6e->ip6e_len + 1) << 3;
311         if (cmsg->cmsg_len < CMSG_SPACE(hdrlen))
312                 return(-1);     
313
314         /*
315          * If the caller does not specify the starting point,
316          * search from the beginning of the option list.
317          * Otherwise, search from *the next option* of the specified point.
318          */
319         lim = (u_int8_t *)ip6e + hdrlen;
320         if (*tptrp == NULL)
321                 *tptrp = (u_int8_t *)(ip6e + 1);
322         else {
323                 if ((optlen = ip6optlen(*tptrp, lim)) == 0)
324                         return(-1);
325
326                 *tptrp = *tptrp + optlen;
327         }
328         for (optp = *tptrp; optp < lim; optp += optlen) {
329                 if (*optp == type) {
330                         *tptrp = optp;
331                         return(0);
332                 }
333                 if ((optlen = ip6optlen(optp, lim)) == 0)
334                         return(-1);
335         }
336
337         /* search failed */
338         *tptrp = NULL;
339         return(-1);
340 }
341
342 /*
343  * Calculate the length of a given IPv6 option. Also checks
344  * if the option is safely stored in user's buffer according to the
345  * calculated length and the limitation of the buffer.
346  */
347 static int
348 ip6optlen(u_int8_t *opt, u_int8_t *lim)
349 {
350         int optlen;
351
352         if (*opt == IP6OPT_PAD1)
353                 optlen = 1;
354         else {
355                 /* is there enough space to store type and len? */
356                 if (opt + 2 > lim)
357                         return(0);
358                 optlen = *(opt + 1) + 2;
359         }
360         if (opt + optlen <= lim)
361                 return(optlen);
362
363         return(0);
364 }
365
366 static void
367 inet6_insert_padopt(u_char *p, int len)
368 {
369         switch(len) {
370          case 0:
371                  return;
372          case 1:
373                  p[0] = IP6OPT_PAD1;
374                  return;
375          default:
376                  p[0] = IP6OPT_PADN;
377                  p[1] = len - 2; 
378                  memset(&p[2], 0, len - 2);
379                  return;
380         }
381 }
382
383 /*
384  * The following functions are defined in RFC3542, which is a successor
385  * of RFC2292.
386  */
387
388 int
389 inet6_opt_init(void *extbuf, socklen_t extlen)
390 {
391         struct ip6_ext *ext = (struct ip6_ext *)extbuf;
392
393         if (ext) {
394                 if (extlen <= 0 || (extlen % 8))
395                         return(-1);
396                 ext->ip6e_len = (extlen >> 3) - 1;
397         }
398
399         return(2);              /* sizeof the next and the length fields */
400 }
401
402 int
403 inet6_opt_append(void *extbuf, socklen_t extlen, int offset, u_int8_t type,
404                  socklen_t len, u_int8_t align, void **databufp)
405 {
406         int currentlen = offset, padlen = 0;
407
408         /*
409          * The option type must have a value from 2 to 255, inclusive.
410          * (0 and 1 are reserved for the Pad1 and PadN options, respectively.)
411          */
412         if (type < 2)
413                 return(-1);
414
415         /*
416          * The option data length must have a value between 0 and 255,
417          * inclusive, and is the length of the option data that follows.
418          */
419         if (len > 255 || len < 0 )
420                 return(-1);
421
422         /*
423          * The align parameter must have a value of 1, 2, 4, or 8.
424          * The align value can not exceed the value of len.
425          */
426         if (align != 1 && align != 2 && align != 4 && align != 8)
427                 return(-1);
428         if (align > len)
429                 return(-1);
430
431         /* Calculate the padding length. */
432         currentlen += 2 + len;  /* 2 means "type + len" */
433         if (currentlen % align)
434                 padlen = align - (currentlen % align);
435
436         /* The option must fit in the extension header buffer. */
437         currentlen += padlen;
438         if (extlen &&           /* XXX: right? */
439             currentlen > extlen)
440                 return(-1);
441
442         if (extbuf) {
443                 u_int8_t *optp = (u_int8_t *)extbuf + offset;
444
445                 if (padlen == 1) {
446                         /* insert a Pad1 option */
447                         *optp = IP6OPT_PAD1;
448                         optp++;
449                 }
450                 else if (padlen > 0) {
451                         /* insert a PadN option for alignment */
452                         *optp++ = IP6OPT_PADN;
453                         *optp++ = padlen - 2;
454                         memset(optp, 0, padlen - 2);
455                         optp += (padlen - 2);
456                 }
457
458                 *optp++ = type;
459                 *optp++ = len;
460
461                 *databufp = optp;
462         }
463
464         return(currentlen);
465 }
466
467 int
468 inet6_opt_finish(void *extbuf, socklen_t extlen, int offset)
469 {
470         int updatelen = offset > 0 ? (1 + ((offset - 1) | 7)) : 0;
471
472         if (extbuf) {
473                 u_int8_t *padp;
474                 int padlen = updatelen - offset;
475
476                 if (updatelen > extlen)
477                         return(-1);
478
479                 padp = (u_int8_t *)extbuf + offset;
480                 if (padlen == 1)
481                         *padp = IP6OPT_PAD1;
482                 else if (padlen > 0) {
483                         *padp++ = IP6OPT_PADN;
484                         *padp++ = (padlen - 2);
485                         memset(padp, 0, padlen - 2);
486                 }
487         }
488
489         return(updatelen);
490 }
491
492 int
493 inet6_opt_set_val(void *databuf, int offset, void *val, socklen_t vallen)
494 {
495
496         memcpy((u_int8_t *)databuf + offset, val, vallen);
497         return(offset + vallen);
498 }
499
500 int
501 inet6_opt_next(void *extbuf, socklen_t extlen, int offset, u_int8_t *typep,
502                socklen_t *lenp, void **databufp)
503 {
504         u_int8_t *optp, *lim;
505         int optlen;
506
507         /* Validate extlen. XXX: is the variable really necessary?? */
508         if (extlen == 0 || (extlen % 8))
509                 return(-1);
510         lim = (u_int8_t *)extbuf + extlen;
511
512         /*
513          * If this is the first time this function called for this options
514          * header, simply return the 1st option.
515          * Otherwise, search the option list for the next option.
516          */
517         if (offset == 0) {
518                 optp = (u_int8_t *)((struct ip6_hbh *)extbuf + 1);
519         }
520         else
521                 optp = (u_int8_t *)extbuf + offset;
522
523         /* Find the next option skipping any padding options. */
524         while(optp < lim) {
525                 switch(*optp) {
526                 case IP6OPT_PAD1:
527                         optp++;
528                         break;
529                 case IP6OPT_PADN:
530                         if ((optlen = ip6optlen(optp, lim)) == 0)
531                                 goto optend;
532                         optp += optlen;
533                         break;
534                 default:        /* found */
535                         if ((optlen = ip6optlen(optp, lim)) == 0)
536                                 goto optend;
537                         *typep = *optp;
538                         *lenp = optlen - 2;
539                         *databufp = optp + 2;
540                         return(optp + optlen - (u_int8_t *)extbuf);
541                 }
542         }
543
544   optend:
545         *databufp = NULL; /* for safety */
546         return(-1);
547 }
548
549 int
550 inet6_opt_find(void *extbuf, socklen_t extlen, int offset, u_int8_t type,
551                socklen_t *lenp, void **databufp)
552 {
553         u_int8_t *optp, *lim;
554         int optlen;
555
556         /* Validate extlen. XXX: is the variable really necessary?? */
557         if (extlen == 0 || (extlen % 8))
558                 return(-1);
559         lim = (u_int8_t *)extbuf + extlen;
560
561         /*
562          * If this is the first time this function called for this options
563          * header, simply return the 1st option.
564          * Otherwise, search the option list for the next option.
565          */
566         if (offset == 0) {
567                 optp = (u_int8_t *)((struct ip6_hbh *)extbuf + 1);
568         }
569         else
570                 optp = (u_int8_t *)extbuf + offset;
571
572         /* Find the specified option */
573         while(optp < lim) {
574                 if ((optlen = ip6optlen(optp, lim)) == 0)
575                         goto optend;
576
577                 if (*optp == type) { /* found */
578                         *lenp = optlen - 2;
579                         *databufp = optp + 2;
580                         return(optp + optlen - (u_int8_t *)extbuf);
581                 }
582
583                 optp += optlen;
584         }
585
586   optend:
587         *databufp = NULL; /* for safety */
588         return(-1);
589 }
590
591 int
592 inet6_opt_get_val(void *databuf, int offset, void *val, socklen_t vallen)
593 {
594
595         /* we can't assume alignment here */
596         memcpy(val, (u_int8_t *)databuf + offset, vallen);
597
598         return(offset + vallen);
599 }