ipv6: make fragment identifications less predictable
Eric Dumazet [Fri, 22 Jul 2011 04:25:58 +0000 (21:25 -0700)]
IPv6 fragment identification generation is way beyond what we use for
IPv4 : It uses a single generator. Its not scalable and allows DOS
attacks.

Now inetpeer is IPv6 aware, we can use it to provide a more secure and
scalable frag ident generator (per destination, instead of system wide)

This patch :
1) defines a new secure_ipv6_id() helper
2) extends inet_getid() to provide 32bit results
3) extends ipv6_select_ident() with a new dest parameter

Reported-by: Fernando Gont <fernando@gont.com.ar>
Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>

drivers/char/random.c
include/linux/random.h
include/net/inetpeer.h
include/net/ipv6.h
net/ipv4/inetpeer.c
net/ipv6/ip6_output.c
net/ipv6/udp.c

index d4ddeba..7292819 100644 (file)
@@ -1523,6 +1523,21 @@ __u32 secure_ip_id(__be32 daddr)
        return half_md4_transform(hash, keyptr->secret);
 }
 
+__u32 secure_ipv6_id(const __be32 daddr[4])
+{
+       const struct keydata *keyptr;
+       __u32 hash[4];
+
+       keyptr = get_keyptr();
+
+       hash[0] = (__force __u32)daddr[0];
+       hash[1] = (__force __u32)daddr[1];
+       hash[2] = (__force __u32)daddr[2];
+       hash[3] = (__force __u32)daddr[3];
+
+       return half_md4_transform(hash, keyptr->secret);
+}
+
 #ifdef CONFIG_INET
 
 __u32 secure_tcp_sequence_number(__be32 saddr, __be32 daddr,
index fb7ab9d..ce29a04 100644 (file)
@@ -58,6 +58,7 @@ extern void get_random_bytes(void *buf, int nbytes);
 void generate_random_uuid(unsigned char uuid_out[16]);
 
 extern __u32 secure_ip_id(__be32 daddr);
+extern __u32 secure_ipv6_id(const __be32 daddr[4]);
 extern u32 secure_ipv4_port_ephemeral(__be32 saddr, __be32 daddr, __be16 dport);
 extern u32 secure_ipv6_port_ephemeral(const __be32 *saddr, const __be32 *daddr,
                                      __be16 dport);
index 39d1230..4233e6f 100644 (file)
@@ -71,7 +71,7 @@ static inline bool inet_metrics_new(const struct inet_peer *p)
 }
 
 /* can be called with or without local BH being disabled */
-struct inet_peer       *inet_getpeer(struct inetpeer_addr *daddr, int create);
+struct inet_peer       *inet_getpeer(const struct inetpeer_addr *daddr, int create);
 
 static inline struct inet_peer *inet_getpeer_v4(__be32 v4daddr, int create)
 {
@@ -106,11 +106,18 @@ static inline void inet_peer_refcheck(const struct inet_peer *p)
 
 
 /* can be called with or without local BH being disabled */
-static inline __u16    inet_getid(struct inet_peer *p, int more)
+static inline int inet_getid(struct inet_peer *p, int more)
 {
+       int old, new;
        more++;
        inet_peer_refcheck(p);
-       return atomic_add_return(more, &p->ip_id_count) - more;
+       do {
+               old = atomic_read(&p->ip_id_count);
+               new = old + more;
+               if (!new)
+                       new = 1;
+       } while (atomic_cmpxchg(&p->ip_id_count, old, new) != old);
+       return new;
 }
 
 #endif /* _NET_INETPEER_H */
index c033ed0..3b5ac1f 100644 (file)
@@ -463,17 +463,7 @@ static inline int ipv6_addr_diff(const struct in6_addr *a1, const struct in6_add
        return __ipv6_addr_diff(a1, a2, sizeof(struct in6_addr));
 }
 
-static __inline__ void ipv6_select_ident(struct frag_hdr *fhdr)
-{
-       static u32 ipv6_fragmentation_id = 1;
-       static DEFINE_SPINLOCK(ip6_id_lock);
-
-       spin_lock_bh(&ip6_id_lock);
-       fhdr->identification = htonl(ipv6_fragmentation_id);
-       if (++ipv6_fragmentation_id == 0)
-               ipv6_fragmentation_id = 1;
-       spin_unlock_bh(&ip6_id_lock);
-}
+extern void ipv6_select_ident(struct frag_hdr *fhdr, struct rt6_info *rt);
 
 /*
  *     Prototypes exported by ipv6
index 90c5f0d..e382138 100644 (file)
@@ -391,7 +391,7 @@ static int inet_peer_gc(struct inet_peer_base *base,
        return cnt;
 }
 
-struct inet_peer *inet_getpeer(struct inetpeer_addr *daddr, int create)
+struct inet_peer *inet_getpeer(const struct inetpeer_addr *daddr, int create)
 {
        struct inet_peer __rcu **stack[PEER_MAXDEPTH], ***stackptr;
        struct inet_peer_base *base = family_to_base(daddr->family);
@@ -436,7 +436,10 @@ relookup:
                p->daddr = *daddr;
                atomic_set(&p->refcnt, 1);
                atomic_set(&p->rid, 0);
-               atomic_set(&p->ip_id_count, secure_ip_id(daddr->addr.a4));
+               atomic_set(&p->ip_id_count,
+                               (daddr->family == AF_INET) ?
+                                       secure_ip_id(daddr->addr.a4) :
+                                       secure_ipv6_id(daddr->addr.a6));
                p->tcp_ts_stamp = 0;
                p->metrics[RTAX_LOCK-1] = INETPEER_METRICS_NEW;
                p->rate_tokens = 0;
index 8db0e48..32e5339 100644 (file)
@@ -596,6 +596,31 @@ int ip6_find_1stfragopt(struct sk_buff *skb, u8 **nexthdr)
        return offset;
 }
 
+void ipv6_select_ident(struct frag_hdr *fhdr, struct rt6_info *rt)
+{
+       static atomic_t ipv6_fragmentation_id;
+       int old, new;
+
+       if (rt) {
+               struct inet_peer *peer;
+
+               if (!rt->rt6i_peer)
+                       rt6_bind_peer(rt, 1);
+               peer = rt->rt6i_peer;
+               if (peer) {
+                       fhdr->identification = htonl(inet_getid(peer, 0));
+                       return;
+               }
+       }
+       do {
+               old = atomic_read(&ipv6_fragmentation_id);
+               new = old + 1;
+               if (!new)
+                       new = 1;
+       } while (atomic_cmpxchg(&ipv6_fragmentation_id, old, new) != old);
+       fhdr->identification = htonl(new);
+}
+
 int ip6_fragment(struct sk_buff *skb, int (*output)(struct sk_buff *))
 {
        struct sk_buff *frag;
@@ -680,7 +705,7 @@ int ip6_fragment(struct sk_buff *skb, int (*output)(struct sk_buff *))
                skb_reset_network_header(skb);
                memcpy(skb_network_header(skb), tmp_hdr, hlen);
 
-               ipv6_select_ident(fh);
+               ipv6_select_ident(fh, rt);
                fh->nexthdr = nexthdr;
                fh->reserved = 0;
                fh->frag_off = htons(IP6_MF);
@@ -826,7 +851,7 @@ slow_path:
                fh->nexthdr = nexthdr;
                fh->reserved = 0;
                if (!frag_id) {
-                       ipv6_select_ident(fh);
+                       ipv6_select_ident(fh, rt);
                        frag_id = fh->identification;
                } else
                        fh->identification = frag_id;
@@ -1076,7 +1101,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)
+                       int transhdrlen, int mtu,unsigned int flags,
+                       struct rt6_info *rt)
 
 {
        struct sk_buff *skb;
@@ -1120,7 +1146,7 @@ static inline int ip6_ufo_append_data(struct sock *sk,
                skb_shinfo(skb)->gso_size = (mtu - fragheaderlen -
                                             sizeof(struct frag_hdr)) & ~7;
                skb_shinfo(skb)->gso_type = SKB_GSO_UDP;
-               ipv6_select_ident(&fhdr);
+               ipv6_select_ident(&fhdr, rt);
                skb_shinfo(skb)->ip6_frag_id = fhdr.identification;
                __skb_queue_tail(&sk->sk_write_queue, skb);
 
@@ -1286,7 +1312,7 @@ int ip6_append_data(struct sock *sk, int getfrag(void *from, char *to,
 
                        err = ip6_ufo_append_data(sk, getfrag, from, length,
                                                  hh_len, fragheaderlen,
-                                                 transhdrlen, mtu, flags);
+                                                 transhdrlen, mtu, flags, rt);
                        if (err)
                                goto error;
                        return 0;
index 328985c..29213b5 100644 (file)
@@ -1359,7 +1359,7 @@ static struct sk_buff *udp6_ufo_fragment(struct sk_buff *skb, u32 features)
        fptr = (struct frag_hdr *)(skb_network_header(skb) + unfrag_ip6hlen);
        fptr->nexthdr = nexthdr;
        fptr->reserved = 0;
-       ipv6_select_ident(fptr);
+       ipv6_select_ident(fptr, (struct rt6_info *)skb_dst(skb));
 
        /* Fragment the skb. ipv6 header and the remaining fields of the
         * fragment header are updated in ipv6_gso_segment()