ipv6: pointer math error in ip6_tnl_parse_tlv_enc_lim()
[pandora-kernel.git] / net / ipv6 / ip6_tunnel.c
index 1828c27..08e4edc 100644 (file)
@@ -372,18 +372,19 @@ ip6_tnl_dev_uninit(struct net_device *dev)
 static __u16
 parse_tlv_tnl_enc_lim(struct sk_buff *skb, __u8 * raw)
 {
-       const struct ipv6hdr *ipv6h = (const struct ipv6hdr *) raw;
-       __u8 nexthdr = ipv6h->nexthdr;
-       __u16 off = sizeof (*ipv6h);
+       const struct ipv6hdr *ipv6h = (const struct ipv6hdr *)raw;
+       unsigned int nhoff = raw - skb->data;
+       unsigned int off = nhoff + sizeof(*ipv6h);
+       u8 next, nexthdr = ipv6h->nexthdr;
 
        while (ipv6_ext_hdr(nexthdr) && nexthdr != NEXTHDR_NONE) {
-               __u16 optlen = 0;
                struct ipv6_opt_hdr *hdr;
-               if (raw + off + sizeof (*hdr) > skb->data &&
-                   !pskb_may_pull(skb, raw - skb->data + off + sizeof (*hdr)))
+               u16 optlen;
+
+               if (!pskb_may_pull(skb, off + sizeof(*hdr)))
                        break;
 
-               hdr = (struct ipv6_opt_hdr *) (raw + off);
+               hdr = (struct ipv6_opt_hdr *)(skb->data + off);
                if (nexthdr == NEXTHDR_FRAGMENT) {
                        struct frag_hdr *frag_hdr = (struct frag_hdr *) hdr;
                        if (frag_hdr->frag_off)
@@ -394,20 +395,29 @@ parse_tlv_tnl_enc_lim(struct sk_buff *skb, __u8 * raw)
                } else {
                        optlen = ipv6_optlen(hdr);
                }
+               /* cache hdr->nexthdr, since pskb_may_pull() might
+                * invalidate hdr
+                */
+               next = hdr->nexthdr;
                if (nexthdr == NEXTHDR_DEST) {
-                       __u16 i = off + 2;
+                       u16 i = 2;
+
+                       /* Remember : hdr is no longer valid at this point. */
+                       if (!pskb_may_pull(skb, off + optlen))
+                               break;
+
                        while (1) {
                                struct ipv6_tlv_tnl_enc_lim *tel;
 
                                /* No more room for encapsulation limit */
-                               if (i + sizeof (*tel) > off + optlen)
+                               if (i + sizeof(*tel) > optlen)
                                        break;
 
-                               tel = (struct ipv6_tlv_tnl_enc_lim *) &raw[i];
+                               tel = (struct ipv6_tlv_tnl_enc_lim *)(skb->data + off + i);
                                /* return index of option if found and valid */
                                if (tel->type == IPV6_TLV_TNL_ENCAP_LIMIT &&
                                    tel->length == 1)
-                                       return i;
+                                       return i + off - nhoff;
                                /* else jump to next option */
                                if (tel->type)
                                        i += tel->length + 2;
@@ -415,7 +425,7 @@ parse_tlv_tnl_enc_lim(struct sk_buff *skb, __u8 * raw)
                                        i++;
                        }
                }
-               nexthdr = hdr->nexthdr;
+               nexthdr = next;
                off += optlen;
        }
        return 0;
@@ -894,13 +904,22 @@ static int ip6_tnl_xmit2(struct sk_buff *skb,
        struct ipv6_tel_txoption opt;
        struct dst_entry *dst = NULL, *ndst = NULL;
        struct net_device *tdev;
+       bool use_cache = false;
        int mtu;
        unsigned int max_headroom = sizeof(struct ipv6hdr);
        u8 proto;
        int err = -1;
        int pkt_len;
 
-       if (!fl6->flowi6_mark)
+       if (!(t->parms.flags &
+                    (IP6_TNL_F_USE_ORIG_TCLASS | IP6_TNL_F_USE_ORIG_FWMARK))) {
+               /* enable the cache only only if the routing decision does
+                * not depend on the current inner header value
+                */
+               use_cache = true;
+       }
+
+       if (use_cache)
                dst = ip6_tnl_dst_check(t);
        if (!dst) {
                ndst = ip6_route_output(net, NULL, fl6);
@@ -959,7 +978,7 @@ static int ip6_tnl_xmit2(struct sk_buff *skb,
                skb = new_skb;
        }
        skb_dst_drop(skb);
-       if (fl6->flowi6_mark) {
+       if (!use_cache) {
                skb_dst_set(skb, dst);
                ndst = NULL;
        } else {
@@ -983,6 +1002,7 @@ static int ip6_tnl_xmit2(struct sk_buff *skb,
        ipv6_addr_copy(&ipv6h->saddr, &fl6->saddr);
        ipv6_addr_copy(&ipv6h->daddr, &fl6->daddr);
        nf_reset(skb);
+       memset(skb->cb, 0, sizeof(struct inet6_skb_parm));
        pkt_len = skb->len;
        err = ip6_local_out(skb);