From patchwork Tue Aug 23 14:58:29 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: [lucid/fsl-imx51, CVE, 1/1] ipv6: make fragment identifications less predictable Date: Tue, 23 Aug 2011 04:58:29 -0000 From: Andy Whitcroft X-Patchwork-Id: 111129 Message-Id: <1314111512-24177-3-git-send-email-apw@canonical.com> To: kernel-team@lists.ubuntu.com [ Backport of upstream commit 87c48fa3b4630905f98268dde838ee43626a060c ] Fernando Gont reported current IPv6 fragment identification generation was not secure, because using a very predictable system-wide generator, allowing various attacks. IPv4 uses inetpeer cache to address this problem and to get good performance. We'll use this mechanism when IPv6 inetpeer is stable enough in linux-3.1 For the time being, we use jhash on destination address to provide less predictable identifications. Also remove a spinlock and use cmpxchg() to get better SMP performance. Reported-by: Fernando Gont Signed-off-by: Eric Dumazet Signed-off-by: David S. Miller Signed-off-by: Greg Kroah-Hartman CVE-2011-2699 BugLink: http://bugs.launchpad.net/bugs/827685 Signed-off-by: Andy Whitcroft --- include/net/ipv6.h | 2 + include/net/transp_v6.h | 2 + net/ipv6/af_inet6.c | 2 + net/ipv6/ip6_output.c | 48 ++++++++++++++++++++++++++++++++-------------- 4 files changed, 39 insertions(+), 15 deletions(-) diff --git a/include/net/ipv6.h b/include/net/ipv6.h index 2c16189..389e0a6 100644 --- a/include/net/ipv6.h +++ b/include/net/ipv6.h @@ -448,6 +448,8 @@ 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)); } +extern void ipv6_select_ident(struct frag_hdr *fhdr, struct rt6_info *rt); + /* * Prototypes exported by ipv6 */ diff --git a/include/net/transp_v6.h b/include/net/transp_v6.h index bfb240c..917d17a 100644 --- a/include/net/transp_v6.h +++ b/include/net/transp_v6.h @@ -16,6 +16,8 @@ extern struct proto tcpv6_prot; struct flowi; +extern void initialize_hashidentrnd(void); + /* extention headers */ extern int ipv6_exthdrs_init(void); extern void ipv6_exthdrs_exit(void); diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c index 45f9a2a..38b297a 100644 --- a/net/ipv6/af_inet6.c +++ b/net/ipv6/af_inet6.c @@ -1059,6 +1059,8 @@ static int __init inet6_init(void) goto out; } + initialize_hashidentrnd(); + err = proto_register(&tcpv6_prot, 1); if (err) goto out; diff --git a/net/ipv6/ip6_output.c b/net/ipv6/ip6_output.c index 87f8419..e884739 100644 --- a/net/ipv6/ip6_output.c +++ b/net/ipv6/ip6_output.c @@ -55,20 +55,37 @@ #include #include -static int ip6_fragment(struct sk_buff *skb, int (*output)(struct sk_buff *)); +static u32 hashidentrnd __read_mostly; +#define FID_HASH_SZ 16 +static u32 ipv6_fragmentation_id[FID_HASH_SZ]; + +void __init initialize_hashidentrnd(void) +{ + get_random_bytes(&hashidentrnd, sizeof(hashidentrnd)); +} -static __inline__ void ipv6_select_ident(struct sk_buff *skb, struct frag_hdr *fhdr) +static u32 __ipv6_select_ident(const struct in6_addr *addr) { - 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); + u32 newid, oldid, hash = jhash2((u32 *)addr, 4, hashidentrnd); + u32 *pid = &ipv6_fragmentation_id[hash % FID_HASH_SZ]; + + do { + oldid = *pid; + newid = oldid + 1; + if (!(hash + newid)) + newid++; + } while (cmpxchg(pid, oldid, newid) != oldid); + + return hash + newid; } +void ipv6_select_ident(struct frag_hdr *fhdr, struct rt6_info *rt) +{ + fhdr->identification = htonl(__ipv6_select_ident(&rt->rt6i_dst.addr)); +} + +static int ip6_fragment(struct sk_buff *skb, int (*output)(struct sk_buff *)); + int __ip6_local_out(struct sk_buff *skb) { int len; @@ -706,7 +723,7 @@ static 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(skb, fh); + ipv6_select_ident(fh, rt); fh->nexthdr = nexthdr; fh->reserved = 0; fh->frag_off = htons(IP6_MF); @@ -844,7 +861,7 @@ slow_path: fh->nexthdr = nexthdr; fh->reserved = 0; if (!frag_id) { - ipv6_select_ident(skb, fh); + ipv6_select_ident(fh, rt); frag_id = fh->identification; } else fh->identification = frag_id; @@ -1048,7 +1065,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; @@ -1091,7 +1109,7 @@ static inline int ip6_ufo_append_data(struct sock *sk, skb_shinfo(skb)->gso_size = mtu - fragheaderlen - sizeof(struct frag_hdr); skb_shinfo(skb)->gso_type = SKB_GSO_UDP; - ipv6_select_ident(skb, &fhdr); + ipv6_select_ident(&fhdr, rt); skb_shinfo(skb)->ip6_frag_id = fhdr.identification; __skb_queue_tail(&sk->sk_write_queue, skb); @@ -1240,7 +1258,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); + flags, rt); if (err) goto error; return 0;