diff options
Diffstat (limited to 'net/ipv6/exthdrs.c')
| -rw-r--r-- | net/ipv6/exthdrs.c | 233 |
1 files changed, 134 insertions, 99 deletions
diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c index ee9f18102a81..9926c82355aa 100644 --- a/net/ipv6/exthdrs.c +++ b/net/ipv6/exthdrs.c @@ -7,7 +7,7 @@ * Andi Kleen <ak@muc.de> * Alexey Kuznetsov <kuznet@ms2.inr.ac.ru> * - * $Id: exthdrs.c,v 1.10 2000/01/09 02:19:55 davem Exp $ + * $Id: exthdrs.c,v 1.12 2001/01/22 02:36:37 davem Exp $ * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -15,6 +15,11 @@ * 2 of the License, or (at your option) any later version. */ +/* Changes: + * yoshfuji : ensure not to overrun while parsing + * tlv options. + */ + #include <linux/errno.h> #include <linux/types.h> #include <linux/socket.h> @@ -41,15 +46,15 @@ /* * Parsing inbound headers. * - * Parsing function "func" returns pointer to the place, + * Parsing function "func" returns offset wrt skb->nh of the place, * where next nexthdr value is stored or NULL, if parsing - * failed. It should also update skb->h. + * failed. It should also update skb->h tp point at the next header. */ struct hdrtype_proc { int type; - u8* (*func) (struct sk_buff **, u8 *ptr); + int (*func) (struct sk_buff **, int offset); }; /* @@ -63,7 +68,7 @@ struct hdrtype_proc struct tlvtype_proc { int type; - int (*func) (struct sk_buff *, __u8 *ptr); + int (*func) (struct sk_buff *, int offset); }; /********************* @@ -72,12 +77,12 @@ struct tlvtype_proc /* An unknown option is detected, decide what to do */ -int ip6_tlvopt_unknown(struct sk_buff *skb, u8 *opt) +int ip6_tlvopt_unknown(struct sk_buff *skb, int optoff) { - switch ((opt[0] & 0xC0) >> 6) { + switch ((skb->nh.raw[optoff] & 0xC0) >> 6) { case 0: /* ignore */ return 1; - + case 1: /* drop packet */ break; @@ -88,7 +93,7 @@ int ip6_tlvopt_unknown(struct sk_buff *skb, u8 *opt) if (ipv6_addr_is_multicast(&skb->nh.ipv6h->daddr)) break; case 2: /* send ICMP PARM PROB regardless and drop packet */ - icmpv6_param_prob(skb, ICMPV6_UNK_OPTION, opt); + icmpv6_param_prob(skb, ICMPV6_UNK_OPTION, optoff); return 0; }; @@ -98,24 +103,21 @@ int ip6_tlvopt_unknown(struct sk_buff *skb, u8 *opt) /* Parse tlv encoded option header (hop-by-hop or destination) */ -static int ip6_parse_tlv(struct tlvtype_proc *procs, struct sk_buff *skb, - __u8 *nhptr) +static int ip6_parse_tlv(struct tlvtype_proc *procs, struct sk_buff *skb) { struct tlvtype_proc *curr; - u8 *ptr = skb->h.raw; - int len = ((ptr[1]+1)<<3) - 2; + int off = skb->h.raw - skb->nh.raw; + int len = ((skb->h.raw[1]+1)<<3); - ptr += 2; + if ((skb->h.raw + len) - skb->data > skb_headlen(skb)) + goto bad; - if (skb->tail - (ptr + len) < 0) { - kfree_skb(skb); - return 0; - } + len -= 2; while (len > 0) { - int optlen = ptr[1]+2; + int optlen = skb->nh.raw[off+1]+2; - switch (ptr[0]) { + switch (skb->nh.raw[off]) { case IPV6_TLV_PAD0: optlen = 1; break; @@ -124,24 +126,30 @@ static int ip6_parse_tlv(struct tlvtype_proc *procs, struct sk_buff *skb, break; default: /* Other TLV code so scan list */ + if (optlen > len) + goto bad; for (curr=procs; curr->type >= 0; curr++) { - if (curr->type == ptr[0]) { - if (curr->func(skb, ptr) == 0) + if (curr->type == skb->nh.raw[off]) { + /* type specific length/alignment + checks will be perfomed in the + func(). */ + if (curr->func(skb, off) == 0) return 0; break; } } if (curr->type < 0) { - if (ip6_tlvopt_unknown(skb, ptr) == 0) + if (ip6_tlvopt_unknown(skb, off) == 0) return 0; } break; } - ptr += optlen; + off += optlen; len -= optlen; } if (len == 0) return 1; +bad: kfree_skb(skb); return 0; } @@ -155,37 +163,42 @@ struct tlvtype_proc tlvprocdestopt_lst[] = { {-1, NULL} }; -static u8 *ipv6_dest_opt(struct sk_buff **skb_ptr, u8 *nhptr) +static int ipv6_dest_opt(struct sk_buff **skb_ptr, int nhoff) { struct sk_buff *skb=*skb_ptr; struct inet6_skb_parm *opt = (struct inet6_skb_parm *)skb->cb; - struct ipv6_destopt_hdr *hdr = (struct ipv6_destopt_hdr *) skb->h.raw; - opt->dst1 = (u8*)hdr - skb->nh.raw; + if (!pskb_may_pull(skb, (skb->h.raw-skb->data)+8) || + !pskb_may_pull(skb, (skb->h.raw-skb->data)+((skb->h.raw[1]+1)<<3))) { + kfree_skb(skb); + return -1; + } + + opt->dst1 = skb->h.raw - skb->nh.raw; - if (ip6_parse_tlv(tlvprocdestopt_lst, skb, nhptr)) { - skb->h.raw += ((hdr->hdrlen+1)<<3); - return &hdr->nexthdr; + if (ip6_parse_tlv(tlvprocdestopt_lst, skb)) { + skb->h.raw += ((skb->h.raw[1]+1)<<3); + return opt->dst1; } - return NULL; + return -1; } /******************************** NONE header. No data in packet. ********************************/ -static u8 *ipv6_nodata(struct sk_buff **skb_ptr, u8 *nhptr) +static int ipv6_nodata(struct sk_buff **skb_ptr, int nhoff) { kfree_skb(*skb_ptr); - return NULL; + return -1; } /******************************** Routing header. ********************************/ -static u8* ipv6_routing_header(struct sk_buff **skb_ptr, u8 *nhptr) +static int ipv6_routing_header(struct sk_buff **skb_ptr, int nhoff) { struct sk_buff *skb = *skb_ptr; struct inet6_skb_parm *opt = (struct inet6_skb_parm *)skb->cb; @@ -194,36 +207,38 @@ static u8* ipv6_routing_header(struct sk_buff **skb_ptr, u8 *nhptr) int addr_type; int n, i; - struct ipv6_rt_hdr *hdr = (struct ipv6_rt_hdr *) skb->h.raw; + struct ipv6_rt_hdr *hdr; struct rt0_hdr *rthdr; - if (((hdr->hdrlen+1)<<3) > skb->tail - skb->h.raw) { + if (!pskb_may_pull(skb, (skb->h.raw-skb->data)+8) || + !pskb_may_pull(skb, (skb->h.raw-skb->data)+((skb->h.raw[1]+1)<<3))) { IP6_INC_STATS_BH(Ip6InHdrErrors); kfree_skb(skb); - return NULL; + return -1; + } + + hdr = (struct ipv6_rt_hdr *) skb->h.raw; + + if ((ipv6_addr_type(&skb->nh.ipv6h->daddr)&IPV6_ADDR_MULTICAST) || + skb->pkt_type != PACKET_HOST) { + kfree_skb(skb); + return -1; } looped_back: if (hdr->segments_left == 0) { - opt->srcrt = (u8*)hdr - skb->nh.raw; + opt->srcrt = skb->h.raw - skb->nh.raw; skb->h.raw += (hdr->hdrlen + 1) << 3; opt->dst0 = opt->dst1; opt->dst1 = 0; - return &hdr->nexthdr; + return (&hdr->nexthdr) - skb->nh.raw; } - if (hdr->type != IPV6_SRCRT_TYPE_0 || hdr->hdrlen & 0x01) { - u8 *pos = (u8*) hdr; - - if (hdr->type != IPV6_SRCRT_TYPE_0) - pos += 2; - else - pos += 1; - - icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, pos); - return NULL; + if (hdr->type != IPV6_SRCRT_TYPE_0 || (hdr->hdrlen & 0x01)) { + icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, hdr->type != IPV6_SRCRT_TYPE_0 ? 2 : 1); + return -1; } - + /* * This is the routing header forwarding algorithm from * RFC 1883, page 17. @@ -232,8 +247,8 @@ looped_back: n = hdr->hdrlen >> 1; if (hdr->segments_left > n) { - icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, &hdr->segments_left); - return NULL; + icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, (&hdr->segments_left) - skb->nh.raw); + return -1; } /* We are about to mangle packet header. Be careful! @@ -243,12 +258,15 @@ looped_back: struct sk_buff *skb2 = skb_copy(skb, GFP_ATOMIC); kfree_skb(skb); if (skb2 == NULL) - return NULL; + return -1; *skb_ptr = skb = skb2; opt = (struct inet6_skb_parm *)skb2->cb; hdr = (struct ipv6_rt_hdr *) skb2->h.raw; } + if (skb->ip_summed == CHECKSUM_HW) + skb->ip_summed = CHECKSUM_NONE; + i = n - --hdr->segments_left; rthdr = (struct rt0_hdr *) hdr; @@ -257,9 +275,9 @@ looped_back: addr_type = ipv6_addr_type(addr); - if (addr_type == IPV6_ADDR_MULTICAST) { + if (addr_type&IPV6_ADDR_MULTICAST) { kfree_skb(skb); - return NULL; + return -1; } ipv6_addr_copy(&daddr, addr); @@ -270,21 +288,21 @@ looped_back: ip6_route_input(skb); if (skb->dst->error) { skb->dst->input(skb); - return NULL; + return -1; } if (skb->dst->dev->flags&IFF_LOOPBACK) { if (skb->nh.ipv6h->hop_limit <= 1) { icmpv6_send(skb, ICMPV6_TIME_EXCEED, ICMPV6_EXC_HOPLIMIT, 0, skb->dev); kfree_skb(skb); - return NULL; + return -1; } skb->nh.ipv6h->hop_limit--; goto looped_back; } skb->dst->input(skb); - return NULL; + return -1; } /* @@ -374,20 +392,30 @@ ipv6_invert_rthdr(struct sock *sk, struct ipv6_rt_hdr *hdr) and opt->hdrlen is even. Shit! --ANK (980730) */ -static u8 *ipv6_auth_hdr(struct sk_buff **skb_ptr, u8 *nhptr) +static int ipv6_auth_hdr(struct sk_buff **skb_ptr, int nhoff) { struct sk_buff *skb=*skb_ptr; struct inet6_skb_parm *opt = (struct inet6_skb_parm *)skb->cb; - struct ipv6_opt_hdr *hdr = (struct ipv6_opt_hdr *)skb->h.raw; - int len = (hdr->hdrlen+2)<<2; + int len; + + if (!pskb_may_pull(skb, (skb->h.raw-skb->data)+8)) + goto fail; + + len = (skb->h.raw[1]+1)<<2; if (len&7) - return NULL; - opt->auth = (u8*)hdr - skb->nh.raw; - if (skb->h.raw + len > skb->tail) - return NULL; + goto fail; + + if (!pskb_may_pull(skb, (skb->h.raw-skb->data)+len)) + goto fail; + + opt->auth = skb->h.raw - skb->nh.raw; skb->h.raw += len; - return &hdr->nexthdr; + return opt->auth; + +fail: + kfree_skb(skb); + return -1; } /* This list MUST NOT contain entry for NEXTHDR_HOP. @@ -408,22 +436,22 @@ struct hdrtype_proc hdrproc_lst[] = { {-1, NULL} }; -u8 *ipv6_parse_exthdrs(struct sk_buff **skb_in, u8 *nhptr) +int ipv6_parse_exthdrs(struct sk_buff **skb_in, int nhoff) { struct hdrtype_proc *hdrt; - u8 nexthdr = *nhptr; + u8 nexthdr = (*skb_in)->nh.raw[nhoff]; restart: for (hdrt=hdrproc_lst; hdrt->type >= 0; hdrt++) { if (hdrt->type == nexthdr) { - if ((nhptr = hdrt->func(skb_in, nhptr)) != NULL) { - nexthdr = *nhptr; + if ((nhoff = hdrt->func(skb_in, nhoff)) >= 0) { + nexthdr = (*skb_in)->nh.raw[nhoff]; goto restart; } - return NULL; + return -1; } } - return nhptr; + return nhoff; } @@ -433,37 +461,37 @@ restart: /* Router Alert as of draft-ietf-ipngwg-ipv6router-alert-04 */ -static int ipv6_hop_ra(struct sk_buff *skb, u8 *ptr) +static int ipv6_hop_ra(struct sk_buff *skb, int optoff) { - if (ptr[1] == 2) { - ((struct inet6_skb_parm*)skb->cb)->ra = ptr - skb->nh.raw; + if (skb->nh.raw[optoff+1] == 2) { + ((struct inet6_skb_parm*)skb->cb)->ra = optoff; return 1; } if (net_ratelimit()) - printk(KERN_DEBUG "ipv6_hop_ra: wrong RA length %d\n", ptr[1]); + printk(KERN_DEBUG "ipv6_hop_ra: wrong RA length %d\n", skb->nh.raw[optoff+1]); kfree_skb(skb); return 0; } /* Jumbo payload */ -static int ipv6_hop_jumbo(struct sk_buff *skb, u8 *ptr) +static int ipv6_hop_jumbo(struct sk_buff *skb, int optoff) { u32 pkt_len; - if (ptr[1] != 4 || ((ptr-skb->nh.raw)&3) != 2) { + if (skb->nh.raw[optoff+1] != 4 || (optoff&3) != 2) { if (net_ratelimit()) - printk(KERN_DEBUG "ipv6_hop_jumbo: wrong jumbo opt length/alignment %d\n", ptr[1]); + printk(KERN_DEBUG "ipv6_hop_jumbo: wrong jumbo opt length/alignment %d\n", skb->nh.raw[optoff+1]); goto drop; } - pkt_len = ntohl(*(u32*)(ptr+2)); + pkt_len = ntohl(*(u32*)(skb->nh.raw+optoff+2)); if (pkt_len < 0x10000) { - icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, ptr+2); + icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, optoff+2); return 0; } if (skb->nh.ipv6h->payload_len) { - icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, ptr); + icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, optoff); return 0; } @@ -471,7 +499,11 @@ static int ipv6_hop_jumbo(struct sk_buff *skb, u8 *ptr) IP6_INC_STATS_BH(Ip6InTruncatedPkts); goto drop; } - skb_trim(skb, pkt_len + sizeof(struct ipv6hdr)); + if (pkt_len + sizeof(struct ipv6hdr) < skb->len) { + __pskb_trim(skb, pkt_len + sizeof(struct ipv6hdr)); + if (skb->ip_summed == CHECKSUM_HW) + skb->ip_summed = CHECKSUM_NONE; + } return 1; drop: @@ -485,12 +517,12 @@ struct tlvtype_proc tlvprochopopt_lst[] = { {-1, NULL} }; -u8 * ipv6_parse_hopopts(struct sk_buff *skb, u8 *nhptr) +int ipv6_parse_hopopts(struct sk_buff *skb, int nhoff) { ((struct inet6_skb_parm*)skb->cb)->hop = sizeof(struct ipv6hdr); - if (ip6_parse_tlv(tlvprochopopt_lst, skb, nhptr)) - return nhptr+((nhptr[1]+1)<<3); - return NULL; + if (ip6_parse_tlv(tlvprochopopt_lst, skb)) + return sizeof(struct ipv6hdr); + return -1; } /* @@ -684,7 +716,7 @@ ipv6_dup_options(struct sock *sk, struct ipv6_txoptions *opt) * find out if nexthdr is a well-known extension header or a protocol */ -static __inline__ int ipv6_ext_hdr(u8 nexthdr) +int ipv6_ext_hdr(u8 nexthdr) { /* * find out if nexthdr is an extension header or a protocol @@ -739,33 +771,36 @@ static __inline__ int ipv6_ext_hdr(u8 nexthdr) * --ANK (980726) */ -u8 *ipv6_skip_exthdr(struct ipv6_opt_hdr *hdr, u8 *nexthdrp, int len) +int ipv6_skip_exthdr(struct sk_buff *skb, int start, u8 *nexthdrp, int len) { u8 nexthdr = *nexthdrp; while (ipv6_ext_hdr(nexthdr)) { - int hdrlen; + struct ipv6_opt_hdr hdr; + int hdrlen; - if (len < sizeof(struct ipv6_opt_hdr)) - return NULL; + if (len < (int)sizeof(struct ipv6_opt_hdr)) + return -1; if (nexthdr == NEXTHDR_NONE) - return NULL; + return -1; + if (skb_copy_bits(skb, start, &hdr, sizeof(hdr))) + BUG(); if (nexthdr == NEXTHDR_FRAGMENT) { - struct frag_hdr *fhdr = (struct frag_hdr *) hdr; + struct frag_hdr *fhdr = (struct frag_hdr *) &hdr; if (ntohs(fhdr->frag_off) & ~0x7) break; hdrlen = 8; } else if (nexthdr == NEXTHDR_AUTH) - hdrlen = (hdr->hdrlen+2)<<2; + hdrlen = (hdr.hdrlen+2)<<2; else - hdrlen = ipv6_optlen(hdr); + hdrlen = ipv6_optlen(&hdr); - nexthdr = hdr->nexthdr; - hdr = (struct ipv6_opt_hdr *) ((u8*)hdr + hdrlen); + nexthdr = hdr.nexthdr; len -= hdrlen; + start += hdrlen; } *nexthdrp = nexthdr; - return (u8*)hdr; + return start; } |
