ipv6: fix out of bound writes in __ip6_append_data()
[pandora-kernel.git] / net / ipv6 / ip6_output.c
index f79defc..4ce3e3f 100644 (file)
@@ -562,13 +562,12 @@ static void ip6_copy_metadata(struct sk_buff *to, struct sk_buff *from)
 int ip6_find_1stfragopt(struct sk_buff *skb, u8 **nexthdr)
 {
        u16 offset = sizeof(struct ipv6hdr);
-       struct ipv6_opt_hdr *exthdr =
-                               (struct ipv6_opt_hdr *)(ipv6_hdr(skb) + 1);
        unsigned int packet_len = skb->tail - skb->network_header;
        int found_rhdr = 0;
        *nexthdr = &ipv6_hdr(skb)->nexthdr;
 
-       while (offset + 1 <= packet_len) {
+       while (offset <= packet_len) {
+               struct ipv6_opt_hdr *exthdr;
 
                switch (**nexthdr) {
 
@@ -589,13 +588,16 @@ int ip6_find_1stfragopt(struct sk_buff *skb, u8 **nexthdr)
                        return offset;
                }
 
-               offset += ipv6_optlen(exthdr);
-               *nexthdr = &exthdr->nexthdr;
+               if (offset + sizeof(struct ipv6_opt_hdr) > packet_len)
+                       return -EINVAL;
+
                exthdr = (struct ipv6_opt_hdr *)(skb_network_header(skb) +
                                                 offset);
+               offset += ipv6_optlen(exthdr);
+               *nexthdr = &exthdr->nexthdr;
        }
 
-       return offset;
+       return -EINVAL;
 }
 
 void ipv6_select_ident(struct frag_hdr *fhdr, struct rt6_info *rt)
@@ -609,6 +611,8 @@ void ipv6_select_ident(struct frag_hdr *fhdr, struct rt6_info *rt)
                get_random_bytes(&ip6_idents_hashrnd, sizeof(ip6_idents_hashrnd));
        }
        hash = __ipv6_addr_jhash(&rt->rt6i_dst.addr, ip6_idents_hashrnd);
+       hash = __ipv6_addr_jhash(&rt->rt6i_src.addr, hash);
+
        id = ip_idents_reserve(hash, 1);
        fhdr->identification = htonl(id);
 }
@@ -621,12 +625,16 @@ int ip6_fragment(struct sk_buff *skb, int (*output)(struct sk_buff *))
        struct ipv6hdr *tmp_hdr;
        struct frag_hdr *fh;
        unsigned int mtu, hlen, left, len;
+       int hroom, troom;
        __be32 frag_id = 0;
        int ptr, offset = 0, err=0;
        u8 *prevhdr, nexthdr = 0;
        struct net *net = dev_net(skb_dst(skb)->dev);
 
-       hlen = ip6_find_1stfragopt(skb, &prevhdr);
+       err = ip6_find_1stfragopt(skb, &prevhdr);
+       if (err < 0)
+               goto fail;
+       hlen = err;
        nexthdr = *prevhdr;
 
        mtu = ip6_skb_dst_mtu(skb);
@@ -787,6 +795,8 @@ slow_path:
         */
 
        *prevhdr = NEXTHDR_FRAGMENT;
+       hroom = LL_RESERVED_SPACE(rt->dst.dev);
+       troom = rt->dst.dev->needed_tailroom;
 
        /*
         *      Keep copying data until we run out.
@@ -805,7 +815,8 @@ slow_path:
                 *      Allocate buffer.
                 */
 
-               if ((frag = alloc_skb(len+hlen+sizeof(struct frag_hdr)+LL_ALLOCATED_SPACE(rt->dst.dev), GFP_ATOMIC)) == NULL) {
+               if ((frag = alloc_skb(len + hlen + sizeof(struct frag_hdr) +
+                                     hroom + troom, GFP_ATOMIC)) == NULL) {
                        NETDEBUG(KERN_INFO "IPv6: frag: no memory for new fragment!\n");
                        IP6_INC_STATS(net, ip6_dst_idev(skb_dst(skb)),
                                      IPSTATS_MIB_FRAGFAILS);
@@ -818,7 +829,7 @@ slow_path:
                 */
 
                ip6_copy_metadata(frag, skb);
-               skb_reserve(frag, LL_RESERVED_SPACE(rt->dst.dev));
+               skb_reserve(frag, hroom);
                skb_put(frag, len + hlen + sizeof(struct frag_hdr));
                skb_reset_network_header(frag);
                fh = (struct frag_hdr *)(skb_network_header(frag) + hlen);
@@ -1103,9 +1114,8 @@ static inline int ip6_ufo_append_data(struct sock *sk,
                        int getfrag(void *from, char *to, int offset, int len,
                        int odd, struct sk_buff *skb),
                        void *from, int length, int hh_len, int fragheaderlen,
-                       int transhdrlen, int mtu,unsigned int flags,
-                       struct rt6_info *rt)
-
+                       int exthdrlen, int transhdrlen, int mtu,
+                       unsigned int flags, struct rt6_info *rt)
 {
        struct sk_buff *skb;
        int err;
@@ -1130,7 +1140,7 @@ static inline int ip6_ufo_append_data(struct sock *sk,
                skb_put(skb,fragheaderlen + transhdrlen);
 
                /* initialize network header pointer */
-               skb_reset_network_header(skb);
+               skb_set_network_header(skb, exthdrlen);
 
                /* initialize protocol header pointer */
                skb->transport_header = skb->network_header + fragheaderlen;
@@ -1333,9 +1343,10 @@ int ip6_append_data(struct sock *sk, int getfrag(void *from, char *to,
        if (((length > mtu) ||
             (skb && skb_has_frags(skb))) &&
            (sk->sk_protocol == IPPROTO_UDP) &&
-           (rt->dst.dev->features & NETIF_F_UFO)) {
+           (rt->dst.dev->features & NETIF_F_UFO) && !rt->dst.header_len &&
+           (sk->sk_type == SOCK_DGRAM)) {
                err = ip6_ufo_append_data(sk, getfrag, from, length,
-                                         hh_len, fragheaderlen,
+                                         hh_len, fragheaderlen, exthdrlen,
                                          transhdrlen, mtu, flags, rt);
                if (err)
                        goto error;
@@ -1405,6 +1416,11 @@ alloc_new_skb:
                         */
                        alloclen += sizeof(struct frag_hdr);
 
+                       copy = datalen - transhdrlen - fraggap;
+                       if (copy < 0) {
+                               err = -EINVAL;
+                               goto error;
+                       }
                        if (transhdrlen) {
                                skb = sock_alloc_send_skb(sk,
                                                alloclen + hh_len,
@@ -1456,13 +1472,9 @@ alloc_new_skb:
                                data += fraggap;
                                pskb_trim_unique(skb_prev, maxfraglen);
                        }
-                       copy = datalen - transhdrlen - fraggap;
-
-                       if (copy < 0) {
-                               err = -EINVAL;
-                               kfree_skb(skb);
-                               goto error;
-                       } else if (copy > 0 && getfrag(from, data + transhdrlen, offset, copy, fraggap, skb) < 0) {
+                       if (copy > 0 &&
+                           getfrag(from, data + transhdrlen, offset,
+                                   copy, fraggap, skb) < 0) {
                                err = -EFAULT;
                                kfree_skb(skb);
                                goto error;