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 @@ relookup: | ||||||
| 		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 @@ slow_path: | ||||||
| 		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…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Eric Dumazet
				Eric Dumazet