diff --git a/include/net/xfrm.h b/include/net/xfrm.h index c3659bac067fb53ec660ed776a1723c5ec97b548..6e168c12562e573252f1e53f13d42f468e78764b 100644 --- a/include/net/xfrm.h +++ b/include/net/xfrm.h @@ -782,7 +782,6 @@ extern int xfrm4_tunnel_register(struct xfrm_tunnel *handler); extern int xfrm4_tunnel_deregister(struct xfrm_tunnel *handler); extern int xfrm4_tunnel_check_size(struct sk_buff *skb); extern int xfrm6_rcv(struct sk_buff **pskb, unsigned int *nhoffp); -extern int xfrm6_clear_mutable_options(struct sk_buff *skb, u16 *nh_offset, int dir); extern int xfrm_user_policy(struct sock *sk, int optname, u8 *optval, int optlen); void xfrm_policy_init(void); diff --git a/net/ipv6/ah6.c b/net/ipv6/ah6.c index 7252a583c3d39487c128cf6bd6d8b68adcf98032..68dde09b58550f35c0b6536e9bfff28cd6563628 100644 --- a/net/ipv6/ah6.c +++ b/net/ipv6/ah6.c @@ -36,6 +36,114 @@ #include <net/xfrm.h> #include <asm/scatterlist.h> +static int zero_out_mutable_opts(struct ipv6_opt_hdr *opthdr) +{ + u8 *opt = (u8 *)opthdr; + int len = ipv6_optlen(opthdr); + int off = 0; + int optlen = 0; + + off += 2; + len -= 2; + + while (len > 0) { + + switch (opt[off]) { + + case IPV6_TLV_PAD0: + optlen = 1; + break; + default: + if (len < 2) + goto bad; + optlen = opt[off+1]+2; + if (len < optlen) + goto bad; + if (opt[off] & 0x20) + memset(&opt[off+2], 0, opt[off+1]); + break; + } + + off += optlen; + len -= optlen; + } + if (len == 0) + return 1; + +bad: + return 0; +} + +static int ipv6_clear_mutable_options(struct sk_buff *skb, u16 *nh_offset, int dir) +{ + u16 offset = sizeof(struct ipv6hdr); + struct ipv6_opt_hdr *exthdr = (struct ipv6_opt_hdr*)(skb->nh.raw + offset); + unsigned int packet_len = skb->tail - skb->nh.raw; + u8 nexthdr = skb->nh.ipv6h->nexthdr; + u8 nextnexthdr = 0; + + *nh_offset = ((unsigned char *)&skb->nh.ipv6h->nexthdr) - skb->nh.raw; + + while (offset + 1 <= packet_len) { + + switch (nexthdr) { + + case NEXTHDR_HOP: + *nh_offset = offset; + offset += ipv6_optlen(exthdr); + if (!zero_out_mutable_opts(exthdr)) { + if (net_ratelimit()) + printk(KERN_WARNING "overrun hopopts\n"); + return 0; + } + nexthdr = exthdr->nexthdr; + exthdr = (struct ipv6_opt_hdr*)(skb->nh.raw + offset); + break; + + case NEXTHDR_ROUTING: + *nh_offset = offset; + offset += ipv6_optlen(exthdr); + ((struct ipv6_rt_hdr*)exthdr)->segments_left = 0; + nexthdr = exthdr->nexthdr; + exthdr = (struct ipv6_opt_hdr*)(skb->nh.raw + offset); + break; + + case NEXTHDR_DEST: + *nh_offset = offset; + offset += ipv6_optlen(exthdr); + if (!zero_out_mutable_opts(exthdr)) { + if (net_ratelimit()) + printk(KERN_WARNING "overrun destopt\n"); + return 0; + } + nexthdr = exthdr->nexthdr; + exthdr = (struct ipv6_opt_hdr*)(skb->nh.raw + offset); + break; + + case NEXTHDR_AUTH: + if (dir == XFRM_POLICY_OUT) { + memset(((struct ipv6_auth_hdr*)exthdr)->auth_data, 0, + (((struct ipv6_auth_hdr*)exthdr)->hdrlen - 1) << 2); + } + if (exthdr->nexthdr == NEXTHDR_DEST) { + offset += (((struct ipv6_auth_hdr*)exthdr)->hdrlen + 2) << 2; + exthdr = (struct ipv6_opt_hdr*)(skb->nh.raw + offset); + nextnexthdr = exthdr->nexthdr; + if (!zero_out_mutable_opts(exthdr)) { + if (net_ratelimit()) + printk(KERN_WARNING "overrun destopt\n"); + return 0; + } + } + return nexthdr; + default : + return nexthdr; + } + } + + return nexthdr; +} + int ah6_output(struct sk_buff *skb) { int err; @@ -80,7 +188,7 @@ int ah6_output(struct sk_buff *skb) memcpy(iph, skb->data, hdr_len); skb->nh.ipv6h = (struct ipv6hdr*)skb_push(skb, x->props.header_len); memcpy(skb->nh.ipv6h, iph, hdr_len); - nexthdr = xfrm6_clear_mutable_options(skb, &nh_offset, XFRM_POLICY_OUT); + nexthdr = ipv6_clear_mutable_options(skb, &nh_offset, XFRM_POLICY_OUT); if (nexthdr == 0) goto error; @@ -138,20 +246,46 @@ int ah6_output(struct sk_buff *skb) int ah6_input(struct xfrm_state *x, struct xfrm_decap_state *decap, struct sk_buff *skb) { - int ah_hlen; - struct ipv6hdr *iph; + /* + * Before process AH + * [IPv6][Ext1][Ext2][AH][Dest][Payload] + * |<-------------->| hdr_len + * |<------------------------>| cleared_hlen + * + * To erase AH: + * Keeping copy of cleared headers. After AH processing, + * Moving the pointer of skb->nh.raw by using skb_pull as long as AH + * header length. Then copy back the copy as long as hdr_len + * If destination header following AH exists, copy it into after [Ext2]. + * + * |<>|[IPv6][Ext1][Ext2][Dest][Payload] + * There is offset of AH before IPv6 header after the process. + */ + + struct ipv6hdr *iph = skb->nh.ipv6h; struct ipv6_auth_hdr *ah; struct ah_data *ahp; unsigned char *tmp_hdr = NULL; - int hdr_len = skb->h.raw - skb->nh.raw; + u16 hdr_len = skb->data - skb->nh.raw; + u16 ah_hlen; + u16 cleared_hlen = hdr_len; + u16 nh_offset = 0; u8 nexthdr = 0; + u8 *prevhdr; if (!pskb_may_pull(skb, sizeof(struct ip_auth_hdr))) goto out; ah = (struct ipv6_auth_hdr*)skb->data; ahp = x->data; - ah_hlen = (ah->hdrlen + 2) << 2; + nexthdr = ah->nexthdr; + ah_hlen = (ah->hdrlen + 2) << 2; + cleared_hlen += ah_hlen; + + if (nexthdr == NEXTHDR_DEST) { + struct ipv6_opt_hdr *dsthdr = (struct ipv6_opt_hdr*)(skb->data + ah_hlen); + cleared_hlen += ipv6_optlen(dsthdr); + } if (ah_hlen != XFRM_ALIGN8(sizeof(struct ipv6_auth_hdr) + ahp->icv_full_len) && ah_hlen != XFRM_ALIGN8(sizeof(struct ipv6_auth_hdr) + ahp->icv_trunc_len)) @@ -166,12 +300,16 @@ int ah6_input(struct xfrm_state *x, struct xfrm_decap_state *decap, struct sk_bu pskb_expand_head(skb, 0, 0, GFP_ATOMIC)) goto out; - tmp_hdr = kmalloc(hdr_len, GFP_ATOMIC); + tmp_hdr = kmalloc(cleared_hlen, GFP_ATOMIC); if (!tmp_hdr) goto out; - memcpy(tmp_hdr, skb->nh.raw, hdr_len); - ah = (struct ipv6_auth_hdr*)skb->data; - iph = skb->nh.ipv6h; + memcpy(tmp_hdr, skb->nh.raw, cleared_hlen); + ipv6_clear_mutable_options(skb, &nh_offset, XFRM_POLICY_IN); + iph->priority = 0; + iph->flow_lbl[0] = 0; + iph->flow_lbl[1] = 0; + iph->flow_lbl[2] = 0; + iph->hop_limit = 0; { u8 auth_data[ahp->icv_trunc_len]; @@ -187,9 +325,15 @@ int ah6_input(struct xfrm_state *x, struct xfrm_decap_state *decap, struct sk_bu } } - nexthdr = ((struct ipv6hdr*)tmp_hdr)->nexthdr = ah->nexthdr; - skb->nh.raw = skb_pull(skb, (ah->hdrlen+2)<<2); + skb->nh.raw = skb_pull(skb, ah_hlen); memcpy(skb->nh.raw, tmp_hdr, hdr_len); + if (nexthdr == NEXTHDR_DEST) { + memcpy(skb->nh.raw + hdr_len, + tmp_hdr + hdr_len + ah_hlen, + cleared_hlen - hdr_len - ah_hlen); + } + prevhdr = (u8*)(skb->nh.raw + nh_offset); + *prevhdr = nexthdr; skb->nh.ipv6h->payload_len = htons(skb->len - sizeof(struct ipv6hdr)); skb_pull(skb, hdr_len); skb->h.raw = skb->data; diff --git a/net/ipv6/ipv6_syms.c b/net/ipv6/ipv6_syms.c index 85deec35088171a243376d177682c882a9a80d7c..a6eba6e81dbf35ff6e1123e127e0774b5286be29 100644 --- a/net/ipv6/ipv6_syms.c +++ b/net/ipv6/ipv6_syms.c @@ -37,7 +37,6 @@ EXPORT_SYMBOL(in6addr_loopback); EXPORT_SYMBOL(in6_dev_finish_destroy); EXPORT_SYMBOL(ip6_find_1stfragopt); EXPORT_SYMBOL(xfrm6_rcv); -EXPORT_SYMBOL(xfrm6_clear_mutable_options); EXPORT_SYMBOL(rt6_lookup); EXPORT_SYMBOL(fl6_sock_lookup); EXPORT_SYMBOL(ipv6_ext_hdr); diff --git a/net/ipv6/xfrm6_input.c b/net/ipv6/xfrm6_input.c index f0bbc0530a4883669e7cbc7ecf0810ee590f5a15..385af959a2a13c14753453c5791d680f008db725 100644 --- a/net/ipv6/xfrm6_input.c +++ b/net/ipv6/xfrm6_input.c @@ -15,114 +15,6 @@ static kmem_cache_t *secpath_cachep; -static int zero_out_mutable_opts(struct ipv6_opt_hdr *opthdr) -{ - u8 *opt = (u8 *)opthdr; - int len = ipv6_optlen(opthdr); - int off = 0; - int optlen = 0; - - off += 2; - len -= 2; - - while (len > 0) { - - switch (opt[off]) { - - case IPV6_TLV_PAD0: - optlen = 1; - break; - default: - if (len < 2) - goto bad; - optlen = opt[off+1]+2; - if (len < optlen) - goto bad; - if (opt[off] & 0x20) - memset(&opt[off+2], 0, opt[off+1]); - break; - } - - off += optlen; - len -= optlen; - } - if (len == 0) - return 1; - -bad: - return 0; -} - -int xfrm6_clear_mutable_options(struct sk_buff *skb, u16 *nh_offset, int dir) -{ - u16 offset = sizeof(struct ipv6hdr); - struct ipv6_opt_hdr *exthdr = (struct ipv6_opt_hdr*)(skb->nh.raw + offset); - unsigned int packet_len = skb->tail - skb->nh.raw; - u8 nexthdr = skb->nh.ipv6h->nexthdr; - u8 nextnexthdr = 0; - - *nh_offset = ((unsigned char *)&skb->nh.ipv6h->nexthdr) - skb->nh.raw; - - while (offset + 1 <= packet_len) { - - switch (nexthdr) { - - case NEXTHDR_HOP: - *nh_offset = offset; - offset += ipv6_optlen(exthdr); - if (!zero_out_mutable_opts(exthdr)) { - if (net_ratelimit()) - printk(KERN_WARNING "overrun hopopts\n"); - return 0; - } - nexthdr = exthdr->nexthdr; - exthdr = (struct ipv6_opt_hdr*)(skb->nh.raw + offset); - break; - - case NEXTHDR_ROUTING: - *nh_offset = offset; - offset += ipv6_optlen(exthdr); - ((struct ipv6_rt_hdr*)exthdr)->segments_left = 0; - nexthdr = exthdr->nexthdr; - exthdr = (struct ipv6_opt_hdr*)(skb->nh.raw + offset); - break; - - case NEXTHDR_DEST: - *nh_offset = offset; - offset += ipv6_optlen(exthdr); - if (!zero_out_mutable_opts(exthdr)) { - if (net_ratelimit()) - printk(KERN_WARNING "overrun destopt\n"); - return 0; - } - nexthdr = exthdr->nexthdr; - exthdr = (struct ipv6_opt_hdr*)(skb->nh.raw + offset); - break; - - case NEXTHDR_AUTH: - if (dir == XFRM_POLICY_OUT) { - memset(((struct ipv6_auth_hdr*)exthdr)->auth_data, 0, - (((struct ipv6_auth_hdr*)exthdr)->hdrlen - 1) << 2); - } - if (exthdr->nexthdr == NEXTHDR_DEST) { - offset += (((struct ipv6_auth_hdr*)exthdr)->hdrlen + 2) << 2; - exthdr = (struct ipv6_opt_hdr*)(skb->nh.raw + offset); - nextnexthdr = exthdr->nexthdr; - if (!zero_out_mutable_opts(exthdr)) { - if (net_ratelimit()) - printk(KERN_WARNING "overrun destopt\n"); - return 0; - } - } - return nexthdr; - default : - return nexthdr; - } - } - - return nexthdr; -} - int xfrm6_rcv(struct sk_buff **pskb, unsigned int *nhoffp) { struct sk_buff *skb = *pskb; @@ -132,26 +24,12 @@ int xfrm6_rcv(struct sk_buff **pskb, unsigned int *nhoffp) struct xfrm_state *x; int xfrm_nr = 0; int decaps = 0; - struct ipv6hdr *hdr = skb->nh.ipv6h; - unsigned char *tmp_hdr = NULL; - int hdr_len = 0; - u16 nh_offset = 0; int nexthdr = 0; + u8 *prevhdr = NULL; - nh_offset = ((unsigned char*)&skb->nh.ipv6h->nexthdr) - skb->nh.raw; - hdr_len = sizeof(struct ipv6hdr); - - tmp_hdr = kmalloc(hdr_len, GFP_ATOMIC); - if (!tmp_hdr) - goto drop; - memcpy(tmp_hdr, skb->nh.raw, hdr_len); - - nexthdr = xfrm6_clear_mutable_options(skb, &nh_offset, XFRM_POLICY_IN); - hdr->priority = 0; - hdr->flow_lbl[0] = 0; - hdr->flow_lbl[1] = 0; - hdr->flow_lbl[2] = 0; - hdr->hop_limit = 0; + ip6_find_1stfragopt(skb, &prevhdr); + nexthdr = *prevhdr; + *nhoffp = prevhdr - skb->nh.raw; if ((err = xfrm_parse_spi(skb, nexthdr, &spi, &seq)) != 0) goto drop; @@ -204,12 +82,6 @@ int xfrm6_rcv(struct sk_buff **pskb, unsigned int *nhoffp) goto drop; } while (!err); - if (!decaps) { - memcpy(skb->nh.raw, tmp_hdr, hdr_len); - skb->nh.raw[nh_offset] = nexthdr; - skb->nh.ipv6h->payload_len = htons(hdr_len + skb->len - sizeof(struct ipv6hdr)); - } - /* Allocate new secpath or COW existing one. */ if (!skb->sp || atomic_read(&skb->sp->refcnt) != 1) { kmem_cache_t *pool = skb->sp ? skb->sp->pool : secpath_cachep; @@ -243,7 +115,6 @@ int xfrm6_rcv(struct sk_buff **pskb, unsigned int *nhoffp) netif_rx(skb); return -1; } else { - *nhoffp = nh_offset; return 1; } @@ -251,7 +122,6 @@ int xfrm6_rcv(struct sk_buff **pskb, unsigned int *nhoffp) spin_unlock(&x->lock); xfrm_state_put(x); drop: - if (tmp_hdr) kfree(tmp_hdr); while (--xfrm_nr >= 0) xfrm_state_put(xfrm_vec[xfrm_nr].xvec); kfree_skb(skb);