inet: switch IP ID generator to siphash [Linux 3.16.72]

This Linux kernel change "inet: switch IP ID generator to siphash" is included in the Linux 3.16.72 release. This change is authored by Eric Dumazet <edumazet [at] google.com> on Wed Mar 27 12:40:33 2019 -0700. The commit for this change in Linux stable tree is 9ebeec4 (patch) which is from upstream commit df45370. The same Linux upstream change may have been applied to various maintained Linux releases and you can find all Linux releases containing changes from upstream df45370.

inet: switch IP ID generator to siphash

commit df453700e8d81b1bdafdf684365ee2b9431fb702 upstream.

According to Amit Klein and Benny Pinkas, IP ID generation is too weak
and might be used by attackers.

Even with recent net_hash_mix() fix (netns: provide pure entropy for net_hash_mix())
having 64bit key and Jenkins hash is risky.

It is time to switch to siphash and its 128bit keys.

Signed-off-by: Eric Dumazet <edumazet@google.com>
Reported-by: Amit Klein <aksecurity@gmail.com>
Reported-by: Benny Pinkas <benny@pinkas.net>
Signed-off-by: David S. Miller <davem@davemloft.net>
[bwh: Backported to 3.16: adjust context]
Signed-off-by: Ben Hutchings <ben@decadent.org.uk>

There are 50 lines of Linux source code added/deleted in this change. Code changes to Linux kernel are as follows.

 include/linux/siphash.h  |  5 +++++
 include/net/netns/ipv4.h |  2 ++
 net/ipv4/route.c         | 12 +++++++-----
 net/ipv6/output_core.c   | 31 ++++++++++++++++---------------
 4 files changed, 30 insertions(+), 20 deletions(-)

diff --git a/include/linux/siphash.h b/include/linux/siphash.h
index feeb29c..c8c7ae2 100644
--- a/include/linux/siphash.h
+++ b/include/linux/siphash.h
@@ -19,6 +19,11 @@
    u64 key[2];
 } siphash_key_t;

+static inline bool siphash_key_is_zero(const siphash_key_t *key)
+{
+   return !(key->key[0] | key->key[1]);
+}
+
 u64 __siphash_aligned(const void *data, size_t len, const siphash_key_t *key);
 #ifndef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS
 u64 __siphash_unaligned(const void *data, size_t len, const siphash_key_t *key);
diff --git a/include/net/netns/ipv4.h b/include/net/netns/ipv4.h
index 80a1c57..213f53d 100644
--- a/include/net/netns/ipv4.h
+++ b/include/net/netns/ipv4.h
@@ -7,6 +7,7 @@

 #include <linux/uidgid.h>
 #include <net/inet_frag.h>
+#include <linux/siphash.h>

 struct tcpm_hash_bucket;
 struct ctl_table_header;
@@ -98,5 +99,6 @@ struct netns_ipv4 {
 #endif
 #endif
    atomic_t    rt_genid;
+   siphash_key_t   ip_id_key;
 };
 #endif
diff --git a/net/ipv4/route.c b/net/ipv4/route.c
index 5092304..858596c 100644
--- a/net/ipv4/route.c
+++ b/net/ipv4/route.c
@@ -486,15 +486,17 @@ u32 ip_idents_reserve(u32 hash, int segs)

 void __ip_select_ident(struct net *net, struct iphdr *iph, int segs)
 {
-   static u32 ip_idents_hashrnd __read_mostly;
    u32 hash, id;

-   net_get_random_once(&ip_idents_hashrnd, sizeof(ip_idents_hashrnd));
+   /* Note the following code is not safe, but this is okay. */
+   if (unlikely(siphash_key_is_zero(&net->ipv4.ip_id_key)))
+       get_random_bytes(&net->ipv4.ip_id_key,
+                sizeof(net->ipv4.ip_id_key));

-   hash = jhash_3words((__force u32)iph->daddr,
+   hash = siphash_3u32((__force u32)iph->daddr,
                (__force u32)iph->saddr,
-               iph->protocol ^ net_hash_mix(net),
-               ip_idents_hashrnd);
+               iph->protocol,
+               &net->ipv4.ip_id_key);
    id = ip_idents_reserve(hash, segs);
    iph->id = htons(id);
 }
diff --git a/net/ipv6/output_core.c b/net/ipv6/output_core.c
index b1986ac..a8274ab 100644
--- a/net/ipv6/output_core.c
+++ b/net/ipv6/output_core.c
@@ -9,14 +9,24 @@
 #include <net/addrconf.h>
 #include <net/secure_seq.h>

-static u32 __ipv6_select_ident(struct net *net, u32 hashrnd,
+static u32 __ipv6_select_ident(struct net *net,
                   struct in6_addr *dst, struct in6_addr *src)
 {
+   const struct {
+       struct in6_addr dst;
+       struct in6_addr src;
+   } __aligned(SIPHASH_ALIGNMENT) combined = {
+       .dst = *dst,
+       .src = *src,
+   };
    u32 hash, id;

-   hash = __ipv6_addr_jhash(dst, hashrnd);
-   hash = __ipv6_addr_jhash(src, hash);
-   hash ^= net_hash_mix(net);
+   /* Note the following code is not safe, but this is okay. */
+   if (unlikely(siphash_key_is_zero(&net->ipv4.ip_id_key)))
+       get_random_bytes(&net->ipv4.ip_id_key,
+                sizeof(net->ipv4.ip_id_key));
+
+   hash = siphash(&combined, sizeof(combined), &net->ipv4.ip_id_key);

    /* Treat id of 0 as unset and if we get 0 back from ip_idents_reserve,
     * set the hight order instead thus minimizing possible future
@@ -39,7 +49,6 @@ static u32 __ipv6_select_ident(struct net *net, u32 hashrnd,
  */
 void ipv6_proxy_select_ident(struct net *net, struct sk_buff *skb)
 {
-   static u32 ip6_proxy_idents_hashrnd __read_mostly;
    struct in6_addr buf[2];
    struct in6_addr *addrs;
    u32 id;
@@ -51,11 +60,7 @@ void ipv6_proxy_select_ident(struct net *net, struct sk_buff *skb)
    if (!addrs)
        return;

-   net_get_random_once(&ip6_proxy_idents_hashrnd,
-               sizeof(ip6_proxy_idents_hashrnd));
-
-   id = __ipv6_select_ident(net, ip6_proxy_idents_hashrnd,
-                &addrs[1], &addrs[0]);
+   id = __ipv6_select_ident(net, &addrs[1], &addrs[0]);
    skb_shinfo(skb)->ip6_frag_id = htonl(id);
 }
 EXPORT_SYMBOL_GPL(ipv6_proxy_select_ident);
@@ -63,13 +68,9 @@ void ipv6_proxy_select_ident(struct net *net, struct sk_buff *skb)
 void ipv6_select_ident(struct net *net, struct frag_hdr *fhdr,
               struct rt6_info *rt)
 {
-   static u32 ip6_idents_hashrnd __read_mostly;
    u32 id;

-   net_get_random_once(&ip6_idents_hashrnd, sizeof(ip6_idents_hashrnd));
-
-   id = __ipv6_select_ident(net, ip6_idents_hashrnd, &rt->rt6i_dst.addr,
-                &rt->rt6i_src.addr);
+   id = __ipv6_select_ident(net, &rt->rt6i_dst.addr, &rt->rt6i_src.addr);
    fhdr->identification = htonl(id);
 }
 EXPORT_SYMBOL(ipv6_select_ident);

Leave a Reply

Your email address will not be published. Required fields are marked *