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

Commit f2c31e32 authored by Eric Dumazet's avatar Eric Dumazet Committed by David S. Miller
Browse files

net: fix NULL dereferences in check_peer_redir()



Gergely Kalman reported crashes in check_peer_redir().

It appears commit f39925db (ipv4: Cache learned redirect
information in inetpeer.) added a race, leading to possible NULL ptr
dereference.

Since we can now change dst neighbour, we should make sure a reader can
safely use a neighbour.

Add RCU protection to dst neighbour, and make sure check_peer_redir()
can be called safely by different cpus in parallel.

As neighbours are already freed after one RCU grace period, this patch
should not add typical RCU penalty (cache cold effects)

Many thanks to Gergely for providing a pretty report pointing to the
bug.

Reported-by: default avatarGergely Kalman <synapse@hippy.csoma.elte.hu>
Signed-off-by: default avatarEric Dumazet <eric.dumazet@gmail.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 28f4881c
Loading
Loading
Loading
Loading
+13 −4
Original line number Diff line number Diff line
@@ -37,7 +37,7 @@ struct dst_entry {
	unsigned long		_metrics;
	unsigned long		expires;
	struct dst_entry	*path;
	struct neighbour	*_neighbour;
	struct neighbour __rcu	*_neighbour;
#ifdef CONFIG_XFRM
	struct xfrm_state	*xfrm;
#else
@@ -88,12 +88,17 @@ struct dst_entry {

static inline struct neighbour *dst_get_neighbour(struct dst_entry *dst)
{
	return dst->_neighbour;
	return rcu_dereference(dst->_neighbour);
}

static inline struct neighbour *dst_get_neighbour_raw(struct dst_entry *dst)
{
	return rcu_dereference_raw(dst->_neighbour);
}

static inline void dst_set_neighbour(struct dst_entry *dst, struct neighbour *neigh)
{
	dst->_neighbour = neigh;
	rcu_assign_pointer(dst->_neighbour, neigh);
}

extern u32 *dst_cow_metrics_generic(struct dst_entry *dst, unsigned long old);
@@ -382,8 +387,12 @@ static inline void dst_rcu_free(struct rcu_head *head)
static inline void dst_confirm(struct dst_entry *dst)
{
	if (dst) {
		struct neighbour *n = dst_get_neighbour(dst);
		struct neighbour *n;

		rcu_read_lock();
		n = dst_get_neighbour(dst);
		neigh_confirm(n);
		rcu_read_unlock();
	}
}

+8 −2
Original line number Diff line number Diff line
@@ -204,9 +204,15 @@ static inline int ip_finish_output2(struct sk_buff *skb)
		skb = skb2;
	}

	rcu_read_lock();
	neigh = dst_get_neighbour(dst);
	if (neigh)
		return neigh_output(neigh, skb);
	if (neigh) {
		int res = neigh_output(neigh, skb);

		rcu_read_unlock();
		return res;
	}
	rcu_read_unlock();

	if (net_ratelimit())
		printk(KERN_DEBUG "ip_finish_output2: No header cache and no neighbour!\n");
+8 −6
Original line number Diff line number Diff line
@@ -1628,16 +1628,18 @@ static int check_peer_redir(struct dst_entry *dst, struct inet_peer *peer)
{
	struct rtable *rt = (struct rtable *) dst;
	__be32 orig_gw = rt->rt_gateway;
	struct neighbour *n;
	struct neighbour *n, *old_n;

	dst_confirm(&rt->dst);

	neigh_release(dst_get_neighbour(&rt->dst));
	dst_set_neighbour(&rt->dst, NULL);

	rt->rt_gateway = peer->redirect_learned.a4;
	rt_bind_neighbour(rt);
	n = dst_get_neighbour(&rt->dst);

	n = ipv4_neigh_lookup(&rt->dst, &rt->rt_gateway);
	if (IS_ERR(n))
		return PTR_ERR(n);
	old_n = xchg(&rt->dst._neighbour, n);
	if (old_n)
		neigh_release(old_n);
	if (!n || !(n->nud_state & NUD_VALID)) {
		if (n)
			neigh_event_send(n, NULL);
+1 −1
Original line number Diff line number Diff line
@@ -656,7 +656,7 @@ ipv6_add_addr(struct inet6_dev *idev, const struct in6_addr *addr, int pfxlen,
	 * layer address of our nexhop router
	 */

	if (dst_get_neighbour(&rt->dst) == NULL)
	if (dst_get_neighbour_raw(&rt->dst) == NULL)
		ifa->flags &= ~IFA_F_OPTIMISTIC;

	ifa->idev = idev;
+1 −1
Original line number Diff line number Diff line
@@ -1455,7 +1455,7 @@ static int fib6_age(struct rt6_info *rt, void *arg)
			RT6_TRACE("aging clone %p\n", rt);
			return -1;
		} else if ((rt->rt6i_flags & RTF_GATEWAY) &&
			   (!(dst_get_neighbour(&rt->dst)->flags & NTF_ROUTER))) {
			   (!(dst_get_neighbour_raw(&rt->dst)->flags & NTF_ROUTER))) {
			RT6_TRACE("purging route %p via non-router but gateway\n",
				  rt);
			return -1;
Loading