Commit 0638eb57 authored by David S. Miller's avatar David S. Miller

Merge branch 'ipv6-Another-followup-to-the-fib6_info-change'

David Ahern says:

====================
net/ipv6: Another followup to the fib6_info change

Last one - for this week.

Patches 1, 2 and 7 are more cleanup patches - removing dead code,
moving code from a header to near its single caller, and updating
function name.

Patches 3-5 do some refactoring leading up to patch 6 which fixes
a NULL dereference. I have only managed to trigger a panic once, so
I can not definitively confirm it addresses the problem but it seems
pretty clear that it is a race on removing a 'from' reference on
an rt6_info and another path using that 'from' value to do
cookie checking.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 1b80f86e 8ae86971
...@@ -174,7 +174,7 @@ struct fib6_info { ...@@ -174,7 +174,7 @@ struct fib6_info {
struct rt6_info { struct rt6_info {
struct dst_entry dst; struct dst_entry dst;
struct fib6_info *from; struct fib6_info __rcu *from;
struct rt6key rt6i_dst; struct rt6key rt6i_dst;
struct rt6key rt6i_src; struct rt6key rt6i_src;
...@@ -223,39 +223,17 @@ static inline bool fib6_check_expired(const struct fib6_info *f6i) ...@@ -223,39 +223,17 @@ static inline bool fib6_check_expired(const struct fib6_info *f6i)
return false; return false;
} }
static inline void rt6_clean_expires(struct rt6_info *rt)
{
rt->rt6i_flags &= ~RTF_EXPIRES;
rt->dst.expires = 0;
}
static inline void rt6_set_expires(struct rt6_info *rt, unsigned long expires)
{
rt->dst.expires = expires;
rt->rt6i_flags |= RTF_EXPIRES;
}
static inline void rt6_update_expires(struct rt6_info *rt0, int timeout)
{
if (!(rt0->rt6i_flags & RTF_EXPIRES) && rt0->from)
rt0->dst.expires = rt0->from->expires;
dst_set_expires(&rt0->dst, timeout);
rt0->rt6i_flags |= RTF_EXPIRES;
}
/* Function to safely get fn->sernum for passed in rt /* Function to safely get fn->sernum for passed in rt
* and store result in passed in cookie. * and store result in passed in cookie.
* Return true if we can get cookie safely * Return true if we can get cookie safely
* Return false if not * Return false if not
*/ */
static inline bool rt6_get_cookie_safe(const struct fib6_info *f6i, static inline bool fib6_get_cookie_safe(const struct fib6_info *f6i,
u32 *cookie) u32 *cookie)
{ {
struct fib6_node *fn; struct fib6_node *fn;
bool status = false; bool status = false;
rcu_read_lock();
fn = rcu_dereference(f6i->fib6_node); fn = rcu_dereference(f6i->fib6_node);
if (fn) { if (fn) {
...@@ -265,17 +243,22 @@ static inline bool rt6_get_cookie_safe(const struct fib6_info *f6i, ...@@ -265,17 +243,22 @@ static inline bool rt6_get_cookie_safe(const struct fib6_info *f6i,
status = true; status = true;
} }
rcu_read_unlock();
return status; return status;
} }
static inline u32 rt6_get_cookie(const struct rt6_info *rt) static inline u32 rt6_get_cookie(const struct rt6_info *rt)
{ {
struct fib6_info *from;
u32 cookie = 0; u32 cookie = 0;
if (rt->rt6i_flags & RTF_PCPU || rcu_read_lock();
(unlikely(!list_empty(&rt->rt6i_uncached)) && rt->from))
rt6_get_cookie_safe(rt->from, &cookie); from = rcu_dereference(rt->from);
if (from && (rt->rt6i_flags & RTF_PCPU ||
unlikely(!list_empty(&rt->rt6i_uncached))))
fib6_get_cookie_safe(from, &cookie);
rcu_read_unlock();
return cookie; return cookie;
} }
......
...@@ -860,6 +860,31 @@ static struct fib6_node *fib6_add_1(struct net *net, ...@@ -860,6 +860,31 @@ static struct fib6_node *fib6_add_1(struct net *net,
return ln; return ln;
} }
static void fib6_drop_pcpu_from(struct fib6_info *f6i,
const struct fib6_table *table)
{
int cpu;
/* release the reference to this fib entry from
* all of its cached pcpu routes
*/
for_each_possible_cpu(cpu) {
struct rt6_info **ppcpu_rt;
struct rt6_info *pcpu_rt;
ppcpu_rt = per_cpu_ptr(f6i->rt6i_pcpu, cpu);
pcpu_rt = *ppcpu_rt;
if (pcpu_rt) {
struct fib6_info *from;
from = rcu_dereference_protected(pcpu_rt->from,
lockdep_is_held(&table->tb6_lock));
rcu_assign_pointer(pcpu_rt->from, NULL);
fib6_info_release(from);
}
}
}
static void fib6_purge_rt(struct fib6_info *rt, struct fib6_node *fn, static void fib6_purge_rt(struct fib6_info *rt, struct fib6_node *fn,
struct net *net) struct net *net)
{ {
...@@ -887,24 +912,8 @@ static void fib6_purge_rt(struct fib6_info *rt, struct fib6_node *fn, ...@@ -887,24 +912,8 @@ static void fib6_purge_rt(struct fib6_info *rt, struct fib6_node *fn,
lockdep_is_held(&table->tb6_lock)); lockdep_is_held(&table->tb6_lock));
} }
if (rt->rt6i_pcpu) { if (rt->rt6i_pcpu)
int cpu; fib6_drop_pcpu_from(rt, table);
/* release the reference to this fib entry from
* all of its cached pcpu routes
*/
for_each_possible_cpu(cpu) {
struct rt6_info **ppcpu_rt;
struct rt6_info *pcpu_rt;
ppcpu_rt = per_cpu_ptr(rt->rt6i_pcpu, cpu);
pcpu_rt = *ppcpu_rt;
if (pcpu_rt) {
fib6_info_release(pcpu_rt->from);
pcpu_rt->from = NULL;
}
}
}
} }
} }
......
...@@ -962,16 +962,21 @@ static int ip6_dst_lookup_tail(struct net *net, const struct sock *sk, ...@@ -962,16 +962,21 @@ static int ip6_dst_lookup_tail(struct net *net, const struct sock *sk,
* that's why we try it again later. * that's why we try it again later.
*/ */
if (ipv6_addr_any(&fl6->saddr) && (!*dst || !(*dst)->error)) { if (ipv6_addr_any(&fl6->saddr) && (!*dst || !(*dst)->error)) {
struct fib6_info *from;
struct rt6_info *rt; struct rt6_info *rt;
bool had_dst = *dst != NULL; bool had_dst = *dst != NULL;
if (!had_dst) if (!had_dst)
*dst = ip6_route_output(net, sk, fl6); *dst = ip6_route_output(net, sk, fl6);
rt = (*dst)->error ? NULL : (struct rt6_info *)*dst; rt = (*dst)->error ? NULL : (struct rt6_info *)*dst;
err = ip6_route_get_saddr(net, rt ? rt->from : NULL,
&fl6->daddr, rcu_read_lock();
from = rt ? rcu_dereference(rt->from) : NULL;
err = ip6_route_get_saddr(net, from, &fl6->daddr,
sk ? inet6_sk(sk)->srcprefs : 0, sk ? inet6_sk(sk)->srcprefs : 0,
&fl6->saddr); &fl6->saddr);
rcu_read_unlock();
if (err) if (err)
goto out_err_release; goto out_err_release;
......
...@@ -359,7 +359,7 @@ EXPORT_SYMBOL(ip6_dst_alloc); ...@@ -359,7 +359,7 @@ EXPORT_SYMBOL(ip6_dst_alloc);
static void ip6_dst_destroy(struct dst_entry *dst) static void ip6_dst_destroy(struct dst_entry *dst)
{ {
struct rt6_info *rt = (struct rt6_info *)dst; struct rt6_info *rt = (struct rt6_info *)dst;
struct fib6_info *from = rt->from; struct fib6_info *from;
struct inet6_dev *idev; struct inet6_dev *idev;
dst_destroy_metrics_generic(dst); dst_destroy_metrics_generic(dst);
...@@ -371,8 +371,11 @@ static void ip6_dst_destroy(struct dst_entry *dst) ...@@ -371,8 +371,11 @@ static void ip6_dst_destroy(struct dst_entry *dst)
in6_dev_put(idev); in6_dev_put(idev);
} }
rt->from = NULL; rcu_read_lock();
from = rcu_dereference(rt->from);
rcu_assign_pointer(rt->from, NULL);
fib6_info_release(from); fib6_info_release(from);
rcu_read_unlock();
} }
static void ip6_dst_ifdown(struct dst_entry *dst, struct net_device *dev, static void ip6_dst_ifdown(struct dst_entry *dst, struct net_device *dev,
...@@ -402,12 +405,16 @@ static bool __rt6_check_expired(const struct rt6_info *rt) ...@@ -402,12 +405,16 @@ static bool __rt6_check_expired(const struct rt6_info *rt)
static bool rt6_check_expired(const struct rt6_info *rt) static bool rt6_check_expired(const struct rt6_info *rt)
{ {
struct fib6_info *from;
from = rcu_dereference(rt->from);
if (rt->rt6i_flags & RTF_EXPIRES) { if (rt->rt6i_flags & RTF_EXPIRES) {
if (time_after(jiffies, rt->dst.expires)) if (time_after(jiffies, rt->dst.expires))
return true; return true;
} else if (rt->from) { } else if (from) {
return rt->dst.obsolete != DST_OBSOLETE_FORCE_CHK || return rt->dst.obsolete != DST_OBSOLETE_FORCE_CHK ||
fib6_check_expired(rt->from); fib6_check_expired(from);
} }
return false; return false;
} }
...@@ -963,7 +970,7 @@ static void rt6_set_from(struct rt6_info *rt, struct fib6_info *from) ...@@ -963,7 +970,7 @@ static void rt6_set_from(struct rt6_info *rt, struct fib6_info *from)
{ {
rt->rt6i_flags &= ~RTF_EXPIRES; rt->rt6i_flags &= ~RTF_EXPIRES;
fib6_info_hold(from); fib6_info_hold(from);
rt->from = from; rcu_assign_pointer(rt->from, from);
dst_init_metrics(&rt->dst, from->fib6_metrics->metrics, true); dst_init_metrics(&rt->dst, from->fib6_metrics->metrics, true);
if (from->fib6_metrics != &dst_default_metrics) { if (from->fib6_metrics != &dst_default_metrics) {
rt->dst._metrics |= DST_METRICS_REFCOUNTED; rt->dst._metrics |= DST_METRICS_REFCOUNTED;
...@@ -1164,10 +1171,8 @@ static struct rt6_info *ip6_rt_cache_alloc(struct fib6_info *ort, ...@@ -1164,10 +1171,8 @@ static struct rt6_info *ip6_rt_cache_alloc(struct fib6_info *ort,
* Clone the route. * Clone the route.
*/ */
rcu_read_lock();
dev = ip6_rt_get_dev_rcu(ort); dev = ip6_rt_get_dev_rcu(ort);
rt = ip6_dst_alloc(dev_net(dev), dev, 0); rt = ip6_dst_alloc(dev_net(dev), dev, 0);
rcu_read_unlock();
if (!rt) if (!rt)
return NULL; return NULL;
...@@ -1855,14 +1860,11 @@ struct rt6_info *ip6_pol_route(struct net *net, struct fib6_table *table, ...@@ -1855,14 +1860,11 @@ struct rt6_info *ip6_pol_route(struct net *net, struct fib6_table *table,
* the daddr in the skb during the neighbor look-up is different * the daddr in the skb during the neighbor look-up is different
* from the fl6->daddr used to look-up route here. * from the fl6->daddr used to look-up route here.
*/ */
struct rt6_info *uncached_rt; struct rt6_info *uncached_rt;
fib6_info_hold(f6i);
rcu_read_unlock();
uncached_rt = ip6_rt_cache_alloc(f6i, &fl6->daddr, NULL); uncached_rt = ip6_rt_cache_alloc(f6i, &fl6->daddr, NULL);
fib6_info_release(f6i);
rcu_read_unlock();
if (uncached_rt) { if (uncached_rt) {
/* Uncached_rt's refcnt is taken during ip6_rt_cache_alloc() /* Uncached_rt's refcnt is taken during ip6_rt_cache_alloc()
...@@ -2128,8 +2130,7 @@ static bool fib6_check(struct fib6_info *f6i, u32 cookie) ...@@ -2128,8 +2130,7 @@ static bool fib6_check(struct fib6_info *f6i, u32 cookie)
{ {
u32 rt_cookie = 0; u32 rt_cookie = 0;
if ((f6i && !rt6_get_cookie_safe(f6i, &rt_cookie)) || if (!fib6_get_cookie_safe(f6i, &rt_cookie) || rt_cookie != cookie)
rt_cookie != cookie)
return false; return false;
if (fib6_check_expired(f6i)) if (fib6_check_expired(f6i))
...@@ -2138,11 +2139,13 @@ static bool fib6_check(struct fib6_info *f6i, u32 cookie) ...@@ -2138,11 +2139,13 @@ static bool fib6_check(struct fib6_info *f6i, u32 cookie)
return true; return true;
} }
static struct dst_entry *rt6_check(struct rt6_info *rt, u32 cookie) static struct dst_entry *rt6_check(struct rt6_info *rt,
struct fib6_info *from,
u32 cookie)
{ {
u32 rt_cookie = 0; u32 rt_cookie = 0;
if ((rt->from && !rt6_get_cookie_safe(rt->from, &rt_cookie)) || if ((from && !fib6_get_cookie_safe(from, &rt_cookie)) ||
rt_cookie != cookie) rt_cookie != cookie)
return NULL; return NULL;
...@@ -2152,11 +2155,13 @@ static struct dst_entry *rt6_check(struct rt6_info *rt, u32 cookie) ...@@ -2152,11 +2155,13 @@ static struct dst_entry *rt6_check(struct rt6_info *rt, u32 cookie)
return &rt->dst; return &rt->dst;
} }
static struct dst_entry *rt6_dst_from_check(struct rt6_info *rt, u32 cookie) static struct dst_entry *rt6_dst_from_check(struct rt6_info *rt,
struct fib6_info *from,
u32 cookie)
{ {
if (!__rt6_check_expired(rt) && if (!__rt6_check_expired(rt) &&
rt->dst.obsolete == DST_OBSOLETE_FORCE_CHK && rt->dst.obsolete == DST_OBSOLETE_FORCE_CHK &&
fib6_check(rt->from, cookie)) fib6_check(from, cookie))
return &rt->dst; return &rt->dst;
else else
return NULL; return NULL;
...@@ -2164,20 +2169,30 @@ static struct dst_entry *rt6_dst_from_check(struct rt6_info *rt, u32 cookie) ...@@ -2164,20 +2169,30 @@ static struct dst_entry *rt6_dst_from_check(struct rt6_info *rt, u32 cookie)
static struct dst_entry *ip6_dst_check(struct dst_entry *dst, u32 cookie) static struct dst_entry *ip6_dst_check(struct dst_entry *dst, u32 cookie)
{ {
struct dst_entry *dst_ret;
struct fib6_info *from;
struct rt6_info *rt; struct rt6_info *rt;
rt = (struct rt6_info *) dst; rt = container_of(dst, struct rt6_info, dst);
rcu_read_lock();
/* All IPV6 dsts are created with ->obsolete set to the value /* All IPV6 dsts are created with ->obsolete set to the value
* DST_OBSOLETE_FORCE_CHK which forces validation calls down * DST_OBSOLETE_FORCE_CHK which forces validation calls down
* into this function always. * into this function always.
*/ */
if (rt->rt6i_flags & RTF_PCPU || from = rcu_dereference(rt->from);
(unlikely(!list_empty(&rt->rt6i_uncached)) && rt->from))
return rt6_dst_from_check(rt, cookie); if (from && (rt->rt6i_flags & RTF_PCPU ||
unlikely(!list_empty(&rt->rt6i_uncached))))
dst_ret = rt6_dst_from_check(rt, from, cookie);
else else
return rt6_check(rt, cookie); dst_ret = rt6_check(rt, from, cookie);
rcu_read_unlock();
return dst_ret;
} }
static struct dst_entry *ip6_negative_advice(struct dst_entry *dst) static struct dst_entry *ip6_negative_advice(struct dst_entry *dst)
...@@ -2209,18 +2224,38 @@ static void ip6_link_failure(struct sk_buff *skb) ...@@ -2209,18 +2224,38 @@ static void ip6_link_failure(struct sk_buff *skb)
if (rt->rt6i_flags & RTF_CACHE) { if (rt->rt6i_flags & RTF_CACHE) {
if (dst_hold_safe(&rt->dst)) if (dst_hold_safe(&rt->dst))
rt6_remove_exception_rt(rt); rt6_remove_exception_rt(rt);
} else if (rt->from) { } else {
struct fib6_info *from;
struct fib6_node *fn; struct fib6_node *fn;
rcu_read_lock(); rcu_read_lock();
fn = rcu_dereference(rt->from->fib6_node); from = rcu_dereference(rt->from);
if (fn && (rt->rt6i_flags & RTF_DEFAULT)) if (from) {
fn->fn_sernum = -1; fn = rcu_dereference(from->fib6_node);
if (fn && (rt->rt6i_flags & RTF_DEFAULT))
fn->fn_sernum = -1;
}
rcu_read_unlock(); rcu_read_unlock();
} }
} }
} }
static void rt6_update_expires(struct rt6_info *rt0, int timeout)
{
if (!(rt0->rt6i_flags & RTF_EXPIRES)) {
struct fib6_info *from;
rcu_read_lock();
from = rcu_dereference(rt0->from);
if (from)
rt0->dst.expires = from->expires;
rcu_read_unlock();
}
dst_set_expires(&rt0->dst, timeout);
rt0->rt6i_flags |= RTF_EXPIRES;
}
static void rt6_do_update_pmtu(struct rt6_info *rt, u32 mtu) static void rt6_do_update_pmtu(struct rt6_info *rt, u32 mtu)
{ {
struct net *net = dev_net(rt->dst.dev); struct net *net = dev_net(rt->dst.dev);
...@@ -2232,8 +2267,14 @@ static void rt6_do_update_pmtu(struct rt6_info *rt, u32 mtu) ...@@ -2232,8 +2267,14 @@ static void rt6_do_update_pmtu(struct rt6_info *rt, u32 mtu)
static bool rt6_cache_allowed_for_pmtu(const struct rt6_info *rt) static bool rt6_cache_allowed_for_pmtu(const struct rt6_info *rt)
{ {
bool from_set;
rcu_read_lock();
from_set = !!rcu_dereference(rt->from);
rcu_read_unlock();
return !(rt->rt6i_flags & RTF_CACHE) && return !(rt->rt6i_flags & RTF_CACHE) &&
(rt->rt6i_flags & RTF_PCPU || rt->from); (rt->rt6i_flags & RTF_PCPU || from_set);
} }
static void __ip6_rt_update_pmtu(struct dst_entry *dst, const struct sock *sk, static void __ip6_rt_update_pmtu(struct dst_entry *dst, const struct sock *sk,
...@@ -2269,14 +2310,18 @@ static void __ip6_rt_update_pmtu(struct dst_entry *dst, const struct sock *sk, ...@@ -2269,14 +2310,18 @@ static void __ip6_rt_update_pmtu(struct dst_entry *dst, const struct sock *sk,
if (rt6->rt6i_flags & RTF_CACHE) if (rt6->rt6i_flags & RTF_CACHE)
rt6_update_exception_stamp_rt(rt6); rt6_update_exception_stamp_rt(rt6);
} else if (daddr) { } else if (daddr) {
struct fib6_info *from;
struct rt6_info *nrt6; struct rt6_info *nrt6;
nrt6 = ip6_rt_cache_alloc(rt6->from, daddr, saddr); rcu_read_lock();
from = rcu_dereference(rt6->from);
nrt6 = ip6_rt_cache_alloc(from, daddr, saddr);
if (nrt6) { if (nrt6) {
rt6_do_update_pmtu(nrt6, mtu); rt6_do_update_pmtu(nrt6, mtu);
if (rt6_insert_exception(nrt6, rt6->from)) if (rt6_insert_exception(nrt6, from))
dst_release_immediate(&nrt6->dst); dst_release_immediate(&nrt6->dst);
} }
rcu_read_unlock();
} }
} }
...@@ -3209,6 +3254,7 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu ...@@ -3209,6 +3254,7 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu
struct ndisc_options ndopts; struct ndisc_options ndopts;
struct inet6_dev *in6_dev; struct inet6_dev *in6_dev;
struct neighbour *neigh; struct neighbour *neigh;
struct fib6_info *from;
struct rd_msg *msg; struct rd_msg *msg;
int optlen, on_link; int optlen, on_link;
u8 *lladdr; u8 *lladdr;
...@@ -3290,7 +3336,10 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu ...@@ -3290,7 +3336,10 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu
NEIGH_UPDATE_F_ISROUTER)), NEIGH_UPDATE_F_ISROUTER)),
NDISC_REDIRECT, &ndopts); NDISC_REDIRECT, &ndopts);
nrt = ip6_rt_cache_alloc(rt->from, &msg->dest, NULL); rcu_read_lock();
from = rcu_dereference(rt->from);
nrt = ip6_rt_cache_alloc(from, &msg->dest, NULL);
rcu_read_unlock();
if (!nrt) if (!nrt)
goto out; goto out;
...@@ -4672,6 +4721,7 @@ static int inet6_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh, ...@@ -4672,6 +4721,7 @@ static int inet6_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh,
struct net *net = sock_net(in_skb->sk); struct net *net = sock_net(in_skb->sk);
struct nlattr *tb[RTA_MAX+1]; struct nlattr *tb[RTA_MAX+1];
int err, iif = 0, oif = 0; int err, iif = 0, oif = 0;
struct fib6_info *from;
struct dst_entry *dst; struct dst_entry *dst;
struct rt6_info *rt; struct rt6_info *rt;
struct sk_buff *skb; struct sk_buff *skb;
...@@ -4768,15 +4818,21 @@ static int inet6_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh, ...@@ -4768,15 +4818,21 @@ static int inet6_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh,
} }
skb_dst_set(skb, &rt->dst); skb_dst_set(skb, &rt->dst);
rcu_read_lock();
from = rcu_dereference(rt->from);
if (fibmatch) if (fibmatch)
err = rt6_fill_node(net, skb, rt->from, NULL, NULL, NULL, iif, err = rt6_fill_node(net, skb, from, NULL, NULL, NULL, iif,
RTM_NEWROUTE, NETLINK_CB(in_skb).portid, RTM_NEWROUTE, NETLINK_CB(in_skb).portid,
nlh->nlmsg_seq, 0); nlh->nlmsg_seq, 0);
else else
err = rt6_fill_node(net, skb, rt->from, dst, err = rt6_fill_node(net, skb, from, dst, &fl6.daddr,
&fl6.daddr, &fl6.saddr, iif, RTM_NEWROUTE, &fl6.saddr, iif, RTM_NEWROUTE,
NETLINK_CB(in_skb).portid, nlh->nlmsg_seq, NETLINK_CB(in_skb).portid, nlh->nlmsg_seq,
0); 0);
rcu_read_unlock();
if (err < 0) { if (err < 0) {
kfree_skb(skb); kfree_skb(skb);
goto errout; goto errout;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment