Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ipv6: add rcu grace period before freeing fib6_node

We currently keep rt->rt6i_node pointing to the fib6_node for the route.
And some functions make use of this pointer to dereference the fib6_node
from rt structure, e.g. rt6_check(). However, as there is neither
refcount nor rcu taken when dereferencing rt->rt6i_node, it could
potentially cause crashes as rt->rt6i_node could be set to NULL by other
CPUs when doing a route deletion.
This patch introduces an rcu grace period before freeing fib6_node and
makes sure the functions that dereference it takes rcu_read_lock().

Note: there is no "Fixes" tag because this bug was there in a very
early stage.

Signed-off-by: Wei Wang <weiwan@google.com>
Acked-by: Eric Dumazet <edumazet@google.com>
Acked-by: Martin KaFai Lau <kafai@fb.com>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Wei Wang and committed by
David S. Miller
c5cff856 0c8d2d95

+56 -8
+29 -1
include/net/ip6_fib.h
··· 70 70 __u16 fn_flags; 71 71 int fn_sernum; 72 72 struct rt6_info *rr_ptr; 73 + struct rcu_head rcu; 73 74 }; 74 75 75 76 #ifndef CONFIG_IPV6_SUBTREES ··· 168 167 rt0->rt6i_flags |= RTF_EXPIRES; 169 168 } 170 169 170 + /* Function to safely get fn->sernum for passed in rt 171 + * and store result in passed in cookie. 172 + * Return true if we can get cookie safely 173 + * Return false if not 174 + */ 175 + static inline bool rt6_get_cookie_safe(const struct rt6_info *rt, 176 + u32 *cookie) 177 + { 178 + struct fib6_node *fn; 179 + bool status = false; 180 + 181 + rcu_read_lock(); 182 + fn = rcu_dereference(rt->rt6i_node); 183 + 184 + if (fn) { 185 + *cookie = fn->fn_sernum; 186 + status = true; 187 + } 188 + 189 + rcu_read_unlock(); 190 + return status; 191 + } 192 + 171 193 static inline u32 rt6_get_cookie(const struct rt6_info *rt) 172 194 { 195 + u32 cookie = 0; 196 + 173 197 if (rt->rt6i_flags & RTF_PCPU || 174 198 (unlikely(!list_empty(&rt->rt6i_uncached)) && rt->dst.from)) 175 199 rt = (struct rt6_info *)(rt->dst.from); 176 200 177 - return rt->rt6i_node ? rt->rt6i_node->fn_sernum : 0; 201 + rt6_get_cookie_safe(rt, &cookie); 202 + 203 + return cookie; 178 204 } 179 205 180 206 static inline void ip6_rt_put(struct rt6_info *rt)
+16 -4
net/ipv6/ip6_fib.c
··· 148 148 return fn; 149 149 } 150 150 151 - static void node_free(struct fib6_node *fn) 151 + static void node_free_immediate(struct fib6_node *fn) 152 152 { 153 153 kmem_cache_free(fib6_node_kmem, fn); 154 + } 155 + 156 + static void node_free_rcu(struct rcu_head *head) 157 + { 158 + struct fib6_node *fn = container_of(head, struct fib6_node, rcu); 159 + 160 + kmem_cache_free(fib6_node_kmem, fn); 161 + } 162 + 163 + static void node_free(struct fib6_node *fn) 164 + { 165 + call_rcu(&fn->rcu, node_free_rcu); 154 166 } 155 167 156 168 static void rt6_free_pcpu(struct rt6_info *non_pcpu_rt) ··· 613 601 614 602 if (!in || !ln) { 615 603 if (in) 616 - node_free(in); 604 + node_free_immediate(in); 617 605 if (ln) 618 - node_free(ln); 606 + node_free_immediate(ln); 619 607 return ERR_PTR(-ENOMEM); 620 608 } 621 609 ··· 1050 1038 root, and then (in failure) stale node 1051 1039 in main tree. 1052 1040 */ 1053 - node_free(sfn); 1041 + node_free_immediate(sfn); 1054 1042 err = PTR_ERR(sn); 1055 1043 goto failure; 1056 1044 }
+11 -3
net/ipv6/route.c
··· 1289 1289 1290 1290 static struct dst_entry *rt6_check(struct rt6_info *rt, u32 cookie) 1291 1291 { 1292 - if (!rt->rt6i_node || (rt->rt6i_node->fn_sernum != cookie)) 1292 + u32 rt_cookie; 1293 + 1294 + if (!rt6_get_cookie_safe(rt, &rt_cookie) || rt_cookie != cookie) 1293 1295 return NULL; 1294 1296 1295 1297 if (rt6_check_expired(rt)) ··· 1359 1357 if (rt->rt6i_flags & RTF_CACHE) { 1360 1358 if (dst_hold_safe(&rt->dst)) 1361 1359 ip6_del_rt(rt); 1362 - } else if (rt->rt6i_node && (rt->rt6i_flags & RTF_DEFAULT)) { 1363 - rt->rt6i_node->fn_sernum = -1; 1360 + } else { 1361 + struct fib6_node *fn; 1362 + 1363 + rcu_read_lock(); 1364 + fn = rcu_dereference(rt->rt6i_node); 1365 + if (fn && (rt->rt6i_flags & RTF_DEFAULT)) 1366 + fn->fn_sernum = -1; 1367 + rcu_read_unlock(); 1364 1368 } 1365 1369 } 1366 1370 }