Skip to content

Commit c5cff85

Browse files
tracywwnjdavem330
authored andcommitted
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 <[email protected]> Acked-by: Eric Dumazet <[email protected]> Acked-by: Martin KaFai Lau <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent 0c8d2d9 commit c5cff85

File tree

3 files changed

+56
-8
lines changed

3 files changed

+56
-8
lines changed

include/net/ip6_fib.h

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ struct fib6_node {
7070
__u16 fn_flags;
7171
int fn_sernum;
7272
struct rt6_info *rr_ptr;
73+
struct rcu_head rcu;
7374
};
7475

7576
#ifndef CONFIG_IPV6_SUBTREES
@@ -167,13 +168,40 @@ static inline void rt6_update_expires(struct rt6_info *rt0, int timeout)
167168
rt0->rt6i_flags |= RTF_EXPIRES;
168169
}
169170

171+
/* Function to safely get fn->sernum for passed in rt
172+
* and store result in passed in cookie.
173+
* Return true if we can get cookie safely
174+
* Return false if not
175+
*/
176+
static inline bool rt6_get_cookie_safe(const struct rt6_info *rt,
177+
u32 *cookie)
178+
{
179+
struct fib6_node *fn;
180+
bool status = false;
181+
182+
rcu_read_lock();
183+
fn = rcu_dereference(rt->rt6i_node);
184+
185+
if (fn) {
186+
*cookie = fn->fn_sernum;
187+
status = true;
188+
}
189+
190+
rcu_read_unlock();
191+
return status;
192+
}
193+
170194
static inline u32 rt6_get_cookie(const struct rt6_info *rt)
171195
{
196+
u32 cookie = 0;
197+
172198
if (rt->rt6i_flags & RTF_PCPU ||
173199
(unlikely(!list_empty(&rt->rt6i_uncached)) && rt->dst.from))
174200
rt = (struct rt6_info *)(rt->dst.from);
175201

176-
return rt->rt6i_node ? rt->rt6i_node->fn_sernum : 0;
202+
rt6_get_cookie_safe(rt, &cookie);
203+
204+
return cookie;
177205
}
178206

179207
static inline void ip6_rt_put(struct rt6_info *rt)

net/ipv6/ip6_fib.c

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,11 +148,23 @@ static struct fib6_node *node_alloc(void)
148148
return fn;
149149
}
150150

151-
static void node_free(struct fib6_node *fn)
151+
static void node_free_immediate(struct fib6_node *fn)
152+
{
153+
kmem_cache_free(fib6_node_kmem, fn);
154+
}
155+
156+
static void node_free_rcu(struct rcu_head *head)
152157
{
158+
struct fib6_node *fn = container_of(head, struct fib6_node, rcu);
159+
153160
kmem_cache_free(fib6_node_kmem, fn);
154161
}
155162

163+
static void node_free(struct fib6_node *fn)
164+
{
165+
call_rcu(&fn->rcu, node_free_rcu);
166+
}
167+
156168
static void rt6_free_pcpu(struct rt6_info *non_pcpu_rt)
157169
{
158170
int cpu;
@@ -601,9 +613,9 @@ static struct fib6_node *fib6_add_1(struct fib6_node *root,
601613

602614
if (!in || !ln) {
603615
if (in)
604-
node_free(in);
616+
node_free_immediate(in);
605617
if (ln)
606-
node_free(ln);
618+
node_free_immediate(ln);
607619
return ERR_PTR(-ENOMEM);
608620
}
609621

@@ -1038,7 +1050,7 @@ int fib6_add(struct fib6_node *root, struct rt6_info *rt,
10381050
root, and then (in failure) stale node
10391051
in main tree.
10401052
*/
1041-
node_free(sfn);
1053+
node_free_immediate(sfn);
10421054
err = PTR_ERR(sn);
10431055
goto failure;
10441056
}

net/ipv6/route.c

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1289,7 +1289,9 @@ static void rt6_dst_from_metrics_check(struct rt6_info *rt)
12891289

12901290
static struct dst_entry *rt6_check(struct rt6_info *rt, u32 cookie)
12911291
{
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)
12931295
return NULL;
12941296

12951297
if (rt6_check_expired(rt))
@@ -1357,8 +1359,14 @@ static void ip6_link_failure(struct sk_buff *skb)
13571359
if (rt->rt6i_flags & RTF_CACHE) {
13581360
if (dst_hold_safe(&rt->dst))
13591361
ip6_del_rt(rt);
1360-
} else if (rt->rt6i_node && (rt->rt6i_flags & RTF_DEFAULT)) {
1361-
rt->rt6i_node->fn_sernum = -1;
1362+
} else {
1363+
struct fib6_node *fn;
1364+
1365+
rcu_read_lock();
1366+
fn = rcu_dereference(rt->rt6i_node);
1367+
if (fn && (rt->rt6i_flags & RTF_DEFAULT))
1368+
fn->fn_sernum = -1;
1369+
rcu_read_unlock();
13621370
}
13631371
}
13641372
}

0 commit comments

Comments
 (0)