Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

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

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
Loading
Loading
Loading
Loading
+12 −29
Original line number Diff line number Diff line
@@ -174,7 +174,7 @@ struct fib6_info {

struct rt6_info {
	struct dst_entry		dst;
	struct fib6_info		*from;
	struct fib6_info __rcu		*from;

	struct rt6key			rt6i_dst;
	struct rt6key			rt6i_src;
@@ -223,39 +223,17 @@ static inline bool fib6_check_expired(const struct fib6_info *f6i)
	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
 * and store result in passed in cookie.
 * Return true if we can get cookie safely
 * 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)
{
	struct fib6_node *fn;
	bool status = false;

	rcu_read_lock();
	fn = rcu_dereference(f6i->fib6_node);

	if (fn) {
@@ -265,17 +243,22 @@ static inline bool rt6_get_cookie_safe(const struct fib6_info *f6i,
		status = true;
	}

	rcu_read_unlock();
	return status;
}

static inline u32 rt6_get_cookie(const struct rt6_info *rt)
{
	struct fib6_info *from;
	u32 cookie = 0;

	if (rt->rt6i_flags & RTF_PCPU ||
	    (unlikely(!list_empty(&rt->rt6i_uncached)) && rt->from))
		rt6_get_cookie_safe(rt->from, &cookie);
	rcu_read_lock();

	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;
}
+27 −18
Original line number Diff line number Diff line
@@ -860,6 +860,31 @@ static struct fib6_node *fib6_add_1(struct net *net,
	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,
			  struct net *net)
{
@@ -887,24 +912,8 @@ static void fib6_purge_rt(struct fib6_info *rt, struct fib6_node *fn,
				    lockdep_is_held(&table->tb6_lock));
		}

		if (rt->rt6i_pcpu) {
			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(rt->rt6i_pcpu, cpu);
				pcpu_rt = *ppcpu_rt;
				if (pcpu_rt) {
					fib6_info_release(pcpu_rt->from);
					pcpu_rt->from = NULL;
				}
			}
		}
		if (rt->rt6i_pcpu)
			fib6_drop_pcpu_from(rt, table);
	}
}

+7 −2
Original line number Diff line number Diff line
@@ -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.
	 */
	if (ipv6_addr_any(&fl6->saddr) && (!*dst || !(*dst)->error)) {
		struct fib6_info *from;
		struct rt6_info *rt;
		bool had_dst = *dst != NULL;

		if (!had_dst)
			*dst = ip6_route_output(net, sk, fl6);
		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,
					  &fl6->saddr);
		rcu_read_unlock();

		if (err)
			goto out_err_release;

+90 −34
Original line number Diff line number Diff line
@@ -359,7 +359,7 @@ EXPORT_SYMBOL(ip6_dst_alloc);
static void ip6_dst_destroy(struct dst_entry *dst)
{
	struct rt6_info *rt = (struct rt6_info *)dst;
	struct fib6_info *from = rt->from;
	struct fib6_info *from;
	struct inet6_dev *idev;

	dst_destroy_metrics_generic(dst);
@@ -371,8 +371,11 @@ static void ip6_dst_destroy(struct dst_entry *dst)
		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);
	rcu_read_unlock();
}

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)

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 (time_after(jiffies, rt->dst.expires))
			return true;
	} else if (rt->from) {
	} else if (from) {
		return rt->dst.obsolete != DST_OBSOLETE_FORCE_CHK ||
			fib6_check_expired(rt->from);
			fib6_check_expired(from);
	}
	return false;
}
@@ -963,7 +970,7 @@ static void rt6_set_from(struct rt6_info *rt, struct fib6_info *from)
{
	rt->rt6i_flags &= ~RTF_EXPIRES;
	fib6_info_hold(from);
	rt->from = from;
	rcu_assign_pointer(rt->from, from);
	dst_init_metrics(&rt->dst, from->fib6_metrics->metrics, true);
	if (from->fib6_metrics != &dst_default_metrics) {
		rt->dst._metrics |= DST_METRICS_REFCOUNTED;
@@ -1164,10 +1171,8 @@ static struct rt6_info *ip6_rt_cache_alloc(struct fib6_info *ort,
	 *	Clone the route.
	 */

	rcu_read_lock();
	dev = ip6_rt_get_dev_rcu(ort);
	rt = ip6_dst_alloc(dev_net(dev), dev, 0);
	rcu_read_unlock();
	if (!rt)
		return NULL;

@@ -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
		 * from the fl6->daddr used to look-up route here.
		 */

		struct rt6_info *uncached_rt;

		fib6_info_hold(f6i);
		rcu_read_unlock();

		uncached_rt = ip6_rt_cache_alloc(f6i, &fl6->daddr, NULL);
		fib6_info_release(f6i);

		rcu_read_unlock();

		if (uncached_rt) {
			/* 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)
{
	u32 rt_cookie = 0;

	if ((f6i && !rt6_get_cookie_safe(f6i, &rt_cookie)) ||
	     rt_cookie != cookie)
	if (!fib6_get_cookie_safe(f6i, &rt_cookie) || rt_cookie != cookie)
		return false;

	if (fib6_check_expired(f6i))
@@ -2138,11 +2139,13 @@ static bool fib6_check(struct fib6_info *f6i, u32 cookie)
	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;

	if ((rt->from && !rt6_get_cookie_safe(rt->from, &rt_cookie)) ||
	if ((from && !fib6_get_cookie_safe(from, &rt_cookie)) ||
	    rt_cookie != cookie)
		return NULL;

@@ -2152,11 +2155,13 @@ static struct dst_entry *rt6_check(struct rt6_info *rt, u32 cookie)
	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) &&
	    rt->dst.obsolete == DST_OBSOLETE_FORCE_CHK &&
	    fib6_check(rt->from, cookie))
	    fib6_check(from, cookie))
		return &rt->dst;
	else
		return NULL;
@@ -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)
{
	struct dst_entry *dst_ret;
	struct fib6_info *from;
	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
	 * DST_OBSOLETE_FORCE_CHK which forces validation calls down
	 * into this function always.
	 */

	if (rt->rt6i_flags & RTF_PCPU ||
	    (unlikely(!list_empty(&rt->rt6i_uncached)) && rt->from))
		return rt6_dst_from_check(rt, cookie);
	from = rcu_dereference(rt->from);

	if (from && (rt->rt6i_flags & RTF_PCPU ||
	    unlikely(!list_empty(&rt->rt6i_uncached))))
		dst_ret = rt6_dst_from_check(rt, from, cookie);
	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)
@@ -2209,18 +2224,38 @@ static void ip6_link_failure(struct sk_buff *skb)
		if (rt->rt6i_flags & RTF_CACHE) {
			if (dst_hold_safe(&rt->dst))
				rt6_remove_exception_rt(rt);
		} else if (rt->from) {
		} else {
			struct fib6_info *from;
			struct fib6_node *fn;

			rcu_read_lock();
			fn = rcu_dereference(rt->from->fib6_node);
			from = rcu_dereference(rt->from);
			if (from) {
				fn = rcu_dereference(from->fib6_node);
				if (fn && (rt->rt6i_flags & RTF_DEFAULT))
					fn->fn_sernum = -1;
			}
			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)
{
	struct net *net = dev_net(rt->dst.dev);
@@ -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)
{
	bool from_set;

	rcu_read_lock();
	from_set = !!rcu_dereference(rt->from);
	rcu_read_unlock();

	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,
@@ -2269,14 +2310,18 @@ static void __ip6_rt_update_pmtu(struct dst_entry *dst, const struct sock *sk,
		if (rt6->rt6i_flags & RTF_CACHE)
			rt6_update_exception_stamp_rt(rt6);
	} else if (daddr) {
		struct fib6_info *from;
		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) {
			rt6_do_update_pmtu(nrt6, mtu);
			if (rt6_insert_exception(nrt6, rt6->from))
			if (rt6_insert_exception(nrt6, from))
				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
	struct ndisc_options ndopts;
	struct inet6_dev *in6_dev;
	struct neighbour *neigh;
	struct fib6_info *from;
	struct rd_msg *msg;
	int optlen, on_link;
	u8 *lladdr;
@@ -3290,7 +3336,10 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu
				     NEIGH_UPDATE_F_ISROUTER)),
		     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)
		goto out;

@@ -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 nlattr *tb[RTA_MAX+1];
	int err, iif = 0, oif = 0;
	struct fib6_info *from;
	struct dst_entry *dst;
	struct rt6_info *rt;
	struct sk_buff *skb;
@@ -4768,15 +4818,21 @@ static int inet6_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh,
	}

	skb_dst_set(skb, &rt->dst);

	rcu_read_lock();
	from = rcu_dereference(rt->from);

	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,
				    nlh->nlmsg_seq, 0);
	else
		err = rt6_fill_node(net, skb, rt->from, dst,
				    &fl6.daddr, &fl6.saddr, iif, RTM_NEWROUTE,
		err = rt6_fill_node(net, skb, from, dst, &fl6.daddr,
				    &fl6.saddr, iif, RTM_NEWROUTE,
				    NETLINK_CB(in_skb).portid, nlh->nlmsg_seq,
				    0);
	rcu_read_unlock();

	if (err < 0) {
		kfree_skb(skb);
		goto errout;