forked from mirrors/linux
		
	ipv4: fix dst race in sk_dst_get()
When IP route cache had been removed in linux-3.6, we broke assumption that dst entries were all freed after rcu grace period. DST_NOCACHE dst were supposed to be freed from dst_release(). But it appears we want to keep such dst around, either in UDP sockets or tunnels. In sk_dst_get() we need to make sure dst refcount is not 0 before incrementing it, or else we might end up freeing a dst twice. DST_NOCACHE set on a dst does not mean this dst can not be attached to a socket or a tunnel. Then, before actual freeing, we need to observe a rcu grace period to make sure all other cpus can catch the fact the dst is no longer usable. Signed-off-by: Eric Dumazet <edumazet@google.com> Reported-by: Dormando <dormando@rydia.net> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
		
							parent
							
								
									99e72a0fed
								
							
						
					
					
						commit
						f886497212
					
				
					 3 changed files with 18 additions and 16 deletions
				
			
		|  | @ -1730,8 +1730,8 @@ sk_dst_get(struct sock *sk) | |||
| 
 | ||||
| 	rcu_read_lock(); | ||||
| 	dst = rcu_dereference(sk->sk_dst_cache); | ||||
| 	if (dst) | ||||
| 		dst_hold(dst); | ||||
| 	if (dst && !atomic_inc_not_zero(&dst->__refcnt)) | ||||
| 		dst = NULL; | ||||
| 	rcu_read_unlock(); | ||||
| 	return dst; | ||||
| } | ||||
|  |  | |||
|  | @ -269,6 +269,15 @@ struct dst_entry *dst_destroy(struct dst_entry * dst) | |||
| } | ||||
| EXPORT_SYMBOL(dst_destroy); | ||||
| 
 | ||||
| static void dst_destroy_rcu(struct rcu_head *head) | ||||
| { | ||||
| 	struct dst_entry *dst = container_of(head, struct dst_entry, rcu_head); | ||||
| 
 | ||||
| 	dst = dst_destroy(dst); | ||||
| 	if (dst) | ||||
| 		__dst_free(dst); | ||||
| } | ||||
| 
 | ||||
| void dst_release(struct dst_entry *dst) | ||||
| { | ||||
| 	if (dst) { | ||||
|  | @ -276,11 +285,8 @@ void dst_release(struct dst_entry *dst) | |||
| 
 | ||||
| 		newrefcnt = atomic_dec_return(&dst->__refcnt); | ||||
| 		WARN_ON(newrefcnt < 0); | ||||
| 		if (unlikely(dst->flags & DST_NOCACHE) && !newrefcnt) { | ||||
| 			dst = dst_destroy(dst); | ||||
| 			if (dst) | ||||
| 				__dst_free(dst); | ||||
| 		} | ||||
| 		if (unlikely(dst->flags & DST_NOCACHE) && !newrefcnt) | ||||
| 			call_rcu(&dst->rcu_head, dst_destroy_rcu); | ||||
| 	} | ||||
| } | ||||
| EXPORT_SYMBOL(dst_release); | ||||
|  |  | |||
|  | @ -73,12 +73,7 @@ static void __tunnel_dst_set(struct ip_tunnel_dst *idst, | |||
| { | ||||
| 	struct dst_entry *old_dst; | ||||
| 
 | ||||
| 	if (dst) { | ||||
| 		if (dst->flags & DST_NOCACHE) | ||||
| 			dst = NULL; | ||||
| 		else | ||||
| 			dst_clone(dst); | ||||
| 	} | ||||
| 	dst_clone(dst); | ||||
| 	old_dst = xchg((__force struct dst_entry **)&idst->dst, dst); | ||||
| 	dst_release(old_dst); | ||||
| } | ||||
|  | @ -108,13 +103,14 @@ static struct rtable *tunnel_rtable_get(struct ip_tunnel *t, u32 cookie) | |||
| 
 | ||||
| 	rcu_read_lock(); | ||||
| 	dst = rcu_dereference(this_cpu_ptr(t->dst_cache)->dst); | ||||
| 	if (dst && !atomic_inc_not_zero(&dst->__refcnt)) | ||||
| 		dst = NULL; | ||||
| 	if (dst) { | ||||
| 		if (dst->obsolete && dst->ops->check(dst, cookie) == NULL) { | ||||
| 			rcu_read_unlock(); | ||||
| 			tunnel_dst_reset(t); | ||||
| 			return NULL; | ||||
| 			dst_release(dst); | ||||
| 			dst = NULL; | ||||
| 		} | ||||
| 		dst_hold(dst); | ||||
| 	} | ||||
| 	rcu_read_unlock(); | ||||
| 	return (struct rtable *)dst; | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Eric Dumazet
						Eric Dumazet