CVE-2011-2699 (C, published 2012-05-24T23:55Z)
Description: value: The IPv6 implementation in the Linux kernel before 3.1 does not generate Fragment Identification values separately for each destination, which makes it easier for remote attackers to cause a denial of service (disrupted networking) by predicting these values and sending crafted packets.
Severity: HIGH (CVSS v3 base score: 7.5)
Weakness: NVD-CWE-Other \u2014 Other
NVD is only using a subset of CWE for mapping instead of the entire CWE, and the weakness type is not covered by that subset.
Repository: https://github.com/torvalds/linux
Files changed:
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
Commit message: ipv6: make fragment identifications less predictable

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>


Vulnerable code:
struct inet_peer	*inet_getpeer(struct inetpeer_addr *daddr, int create);
static inline __u16	inet_getid(struct inet_peer *p, int more)
	return atomic_add_return(more, &p->ip_id_count) - more;
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);
}
struct inet_peer *inet_getpeer(struct inetpeer_addr *daddr, int create)
		atomic_set(&p->ip_id_count, secure_ip_id(daddr->addr.a4));
		ipv6_select_ident(fh);
			ipv6_select_ident(fh);
			int transhdrlen, int mtu,unsigned int flags)
		ipv6_select_ident(&fhdr);
						  transhdrlen, mtu, flags);
	ipv6_select_ident(fptr);

Fixed code:
__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);
}

extern __u32 secure_ipv6_id(const __be32 daddr[4]);
struct inet_peer	*inet_getpeer(const struct inetpeer_addr *daddr, int create);
static inline int inet_getid(struct inet_peer *p, int more)
	int old, new;
	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;
extern void ipv6_select_ident(struct frag_hdr *fhdr, struct rt6_info *rt);
struct inet_peer *inet_getpeer(const struct inetpeer_addr *daddr, int create)
		atomic_set(&p->ip_id_count,
				(daddr->family == AF_INET) ?
					secure_ip_id(daddr->addr.a4) :
					secure_ipv6_id(daddr->addr.a6));
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);
}

		ipv6_select_ident(fh, rt);
			ipv6_select_ident(fh, rt);
			int transhdrlen, int mtu,unsigned int flags,
			struct rt6_info *rt)
		ipv6_select_ident(&fhdr, rt);
						  transhdrlen, mtu, flags, rt);
	ipv6_select_ident(fptr, (struct rt6_info *)skb_dst(skb));