ipv6: A few fixes on dereferencing rt->from

It is a followup after the fix in
commit 9c69a13205 ("route: Avoid crash from dereferencing NULL rt->from")

rt6_do_redirect():
1. NULL checking is needed on rt->from because a parallel
   fib6_info delete could happen that sets rt->from to NULL.
   (e.g. rt6_remove_exception() and fib6_drop_pcpu_from()).

2. fib6_info_hold() is not enough.  Same reason as (1).
   Meaning, holding dst->__refcnt cannot ensure
   rt->from is not NULL or rt->from->fib6_ref is not 0.

   Instead of using fib6_info_hold_safe() which ip6_rt_cache_alloc()
   is already doing, this patch chooses to extend the rcu section
   to keep "from" dereference-able after checking for NULL.

inet6_rtm_getroute():
1. NULL checking is also needed on rt->from for a similar reason.
   Note that inet6_rtm_getroute() is using RTNL_FLAG_DOIT_UNLOCKED.

Fixes: a68886a691 ("net/ipv6: Make from in rt6_info rcu protected")
Signed-off-by: Martin KaFai Lau <kafai@fb.com>
Acked-by: Wei Wang <weiwan@google.com>
Reviewed-by: David Ahern <dsahern@gmail.com>
Reviewed-by: Eric Dumazet <edumazet@google.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Martin KaFai Lau 2019-04-30 10:45:12 -07:00 committed by David S. Miller
parent f3505745c0
commit 886b7a5010
1 changed files with 18 additions and 20 deletions

View File

@ -3392,11 +3392,8 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu
rcu_read_lock(); rcu_read_lock();
from = rcu_dereference(rt->from); from = rcu_dereference(rt->from);
/* This fib6_info_hold() is safe here because we hold reference to rt if (!from)
* and rt already holds reference to fib6_info. goto out;
*/
fib6_info_hold(from);
rcu_read_unlock();
nrt = ip6_rt_cache_alloc(from, &msg->dest, NULL); nrt = ip6_rt_cache_alloc(from, &msg->dest, NULL);
if (!nrt) if (!nrt)
@ -3408,10 +3405,7 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu
nrt->rt6i_gateway = *(struct in6_addr *)neigh->primary_key; nrt->rt6i_gateway = *(struct in6_addr *)neigh->primary_key;
/* No need to remove rt from the exception table if rt is /* rt6_insert_exception() will take care of duplicated exceptions */
* a cached route because rt6_insert_exception() will
* takes care of it
*/
if (rt6_insert_exception(nrt, from)) { if (rt6_insert_exception(nrt, from)) {
dst_release_immediate(&nrt->dst); dst_release_immediate(&nrt->dst);
goto out; goto out;
@ -3424,7 +3418,7 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu
call_netevent_notifiers(NETEVENT_REDIRECT, &netevent); call_netevent_notifiers(NETEVENT_REDIRECT, &netevent);
out: out:
fib6_info_release(from); rcu_read_unlock();
neigh_release(neigh); neigh_release(neigh);
} }
@ -5023,16 +5017,20 @@ static int inet6_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh,
rcu_read_lock(); rcu_read_lock();
from = rcu_dereference(rt->from); from = rcu_dereference(rt->from);
if (from) {
if (fibmatch) if (fibmatch)
err = rt6_fill_node(net, skb, from, NULL, NULL, NULL, iif, err = rt6_fill_node(net, skb, from, NULL, NULL, NULL,
RTM_NEWROUTE, NETLINK_CB(in_skb).portid, iif, RTM_NEWROUTE,
nlh->nlmsg_seq, 0); NETLINK_CB(in_skb).portid,
else nlh->nlmsg_seq, 0);
err = rt6_fill_node(net, skb, from, dst, &fl6.daddr, else
&fl6.saddr, iif, RTM_NEWROUTE, err = rt6_fill_node(net, skb, from, dst, &fl6.daddr,
NETLINK_CB(in_skb).portid, nlh->nlmsg_seq, &fl6.saddr, iif, RTM_NEWROUTE,
0); NETLINK_CB(in_skb).portid,
nlh->nlmsg_seq, 0);
} else {
err = -ENETUNREACH;
}
rcu_read_unlock(); rcu_read_unlock();
if (err < 0) { if (err < 0) {