mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	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>
This commit is contained in:
		
							parent
							
								
									21efcfa0ff
								
							
						
					
					
						commit
						87c48fa3b4
					
				
					 7 changed files with 64 additions and 22 deletions
				
			
		| 
						 | 
					@ -1523,6 +1523,21 @@ __u32 secure_ip_id(__be32 daddr)
 | 
				
			||||||
	return half_md4_transform(hash, keyptr->secret);
 | 
						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
 | 
					#ifdef CONFIG_INET
 | 
				
			||||||
 | 
					
 | 
				
			||||||
__u32 secure_tcp_sequence_number(__be32 saddr, __be32 daddr,
 | 
					__u32 secure_tcp_sequence_number(__be32 saddr, __be32 daddr,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -58,6 +58,7 @@ extern void get_random_bytes(void *buf, int nbytes);
 | 
				
			||||||
void generate_random_uuid(unsigned char uuid_out[16]);
 | 
					void generate_random_uuid(unsigned char uuid_out[16]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
extern __u32 secure_ip_id(__be32 daddr);
 | 
					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_ipv4_port_ephemeral(__be32 saddr, __be32 daddr, __be16 dport);
 | 
				
			||||||
extern u32 secure_ipv6_port_ephemeral(const __be32 *saddr, const __be32 *daddr,
 | 
					extern u32 secure_ipv6_port_ephemeral(const __be32 *saddr, const __be32 *daddr,
 | 
				
			||||||
				      __be16 dport);
 | 
									      __be16 dport);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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 */
 | 
					/* 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)
 | 
					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 */
 | 
					/* 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++;
 | 
						more++;
 | 
				
			||||||
	inet_peer_refcheck(p);
 | 
						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 */
 | 
					#endif /* _NET_INETPEER_H */
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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));
 | 
						return __ipv6_addr_diff(a1, a2, sizeof(struct in6_addr));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static __inline__ void ipv6_select_ident(struct frag_hdr *fhdr)
 | 
					extern void ipv6_select_ident(struct frag_hdr *fhdr, struct rt6_info *rt);
 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	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);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
 *	Prototypes exported by ipv6
 | 
					 *	Prototypes exported by ipv6
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -391,7 +391,7 @@ static int inet_peer_gc(struct inet_peer_base *base,
 | 
				
			||||||
	return cnt;
 | 
						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 __rcu **stack[PEER_MAXDEPTH], ***stackptr;
 | 
				
			||||||
	struct inet_peer_base *base = family_to_base(daddr->family);
 | 
						struct inet_peer_base *base = family_to_base(daddr->family);
 | 
				
			||||||
| 
						 | 
					@ -436,7 +436,10 @@ struct inet_peer *inet_getpeer(struct inetpeer_addr *daddr, int create)
 | 
				
			||||||
		p->daddr = *daddr;
 | 
							p->daddr = *daddr;
 | 
				
			||||||
		atomic_set(&p->refcnt, 1);
 | 
							atomic_set(&p->refcnt, 1);
 | 
				
			||||||
		atomic_set(&p->rid, 0);
 | 
							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->tcp_ts_stamp = 0;
 | 
				
			||||||
		p->metrics[RTAX_LOCK-1] = INETPEER_METRICS_NEW;
 | 
							p->metrics[RTAX_LOCK-1] = INETPEER_METRICS_NEW;
 | 
				
			||||||
		p->rate_tokens = 0;
 | 
							p->rate_tokens = 0;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -596,6 +596,31 @@ int ip6_find_1stfragopt(struct sk_buff *skb, u8 **nexthdr)
 | 
				
			||||||
	return offset;
 | 
						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 *))
 | 
					int ip6_fragment(struct sk_buff *skb, int (*output)(struct sk_buff *))
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct sk_buff *frag;
 | 
						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);
 | 
							skb_reset_network_header(skb);
 | 
				
			||||||
		memcpy(skb_network_header(skb), tmp_hdr, hlen);
 | 
							memcpy(skb_network_header(skb), tmp_hdr, hlen);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		ipv6_select_ident(fh);
 | 
							ipv6_select_ident(fh, rt);
 | 
				
			||||||
		fh->nexthdr = nexthdr;
 | 
							fh->nexthdr = nexthdr;
 | 
				
			||||||
		fh->reserved = 0;
 | 
							fh->reserved = 0;
 | 
				
			||||||
		fh->frag_off = htons(IP6_MF);
 | 
							fh->frag_off = htons(IP6_MF);
 | 
				
			||||||
| 
						 | 
					@ -826,7 +851,7 @@ int ip6_fragment(struct sk_buff *skb, int (*output)(struct sk_buff *))
 | 
				
			||||||
		fh->nexthdr = nexthdr;
 | 
							fh->nexthdr = nexthdr;
 | 
				
			||||||
		fh->reserved = 0;
 | 
							fh->reserved = 0;
 | 
				
			||||||
		if (!frag_id) {
 | 
							if (!frag_id) {
 | 
				
			||||||
			ipv6_select_ident(fh);
 | 
								ipv6_select_ident(fh, rt);
 | 
				
			||||||
			frag_id = fh->identification;
 | 
								frag_id = fh->identification;
 | 
				
			||||||
		} else
 | 
							} else
 | 
				
			||||||
			fh->identification = frag_id;
 | 
								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 getfrag(void *from, char *to, int offset, int len,
 | 
				
			||||||
			int odd, struct sk_buff *skb),
 | 
								int odd, struct sk_buff *skb),
 | 
				
			||||||
			void *from, int length, int hh_len, int fragheaderlen,
 | 
								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;
 | 
						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 -
 | 
							skb_shinfo(skb)->gso_size = (mtu - fragheaderlen -
 | 
				
			||||||
					     sizeof(struct frag_hdr)) & ~7;
 | 
										     sizeof(struct frag_hdr)) & ~7;
 | 
				
			||||||
		skb_shinfo(skb)->gso_type = SKB_GSO_UDP;
 | 
							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_shinfo(skb)->ip6_frag_id = fhdr.identification;
 | 
				
			||||||
		__skb_queue_tail(&sk->sk_write_queue, skb);
 | 
							__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,
 | 
								err = ip6_ufo_append_data(sk, getfrag, from, length,
 | 
				
			||||||
						  hh_len, fragheaderlen,
 | 
											  hh_len, fragheaderlen,
 | 
				
			||||||
						  transhdrlen, mtu, flags);
 | 
											  transhdrlen, mtu, flags, rt);
 | 
				
			||||||
			if (err)
 | 
								if (err)
 | 
				
			||||||
				goto error;
 | 
									goto error;
 | 
				
			||||||
			return 0;
 | 
								return 0;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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 = (struct frag_hdr *)(skb_network_header(skb) + unfrag_ip6hlen);
 | 
				
			||||||
	fptr->nexthdr = nexthdr;
 | 
						fptr->nexthdr = nexthdr;
 | 
				
			||||||
	fptr->reserved = 0;
 | 
						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 the skb. ipv6 header and the remaining fields of the
 | 
				
			||||||
	 * fragment header are updated in ipv6_gso_segment()
 | 
						 * fragment header are updated in ipv6_gso_segment()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue