Commit b0e95ccd authored by David Ahern's avatar David Ahern Committed by David S. Miller

net: vrf: protect changes to private data with rcu

One cpu can be processing packets which includes using the cached route
entries in the vrf device's private data and on another cpu the device
gets deleted which releases the routes and sets the pointers in net_vrf
to NULL. This results in datapath dereferencing a NULL pointer.

Fix by protecting access to dst's with rcu.

Fixes: 193125db ("net: Introduce VRF device driver")
Fixes: 35402e31 ("net: Add IPv6 support to VRF device")
Signed-off-by: default avatarDavid Ahern <dsa@cumulusnetworks.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent ea1627c2
...@@ -43,8 +43,8 @@ ...@@ -43,8 +43,8 @@
#define DRV_VERSION "1.0" #define DRV_VERSION "1.0"
struct net_vrf { struct net_vrf {
struct rtable *rth; struct rtable __rcu *rth;
struct rt6_info *rt6; struct rt6_info __rcu *rt6;
u32 tb_id; u32 tb_id;
}; };
...@@ -273,10 +273,15 @@ static int vrf_output6(struct net *net, struct sock *sk, struct sk_buff *skb) ...@@ -273,10 +273,15 @@ static int vrf_output6(struct net *net, struct sock *sk, struct sk_buff *skb)
!(IP6CB(skb)->flags & IP6SKB_REROUTED)); !(IP6CB(skb)->flags & IP6SKB_REROUTED));
} }
/* holding rtnl */
static void vrf_rt6_release(struct net_vrf *vrf) static void vrf_rt6_release(struct net_vrf *vrf)
{ {
dst_release(&vrf->rt6->dst); struct rt6_info *rt6 = rtnl_dereference(vrf->rt6);
vrf->rt6 = NULL;
rcu_assign_pointer(vrf->rt6, NULL);
if (rt6)
dst_release(&rt6->dst);
} }
static int vrf_rt6_create(struct net_device *dev) static int vrf_rt6_create(struct net_device *dev)
...@@ -300,7 +305,8 @@ static int vrf_rt6_create(struct net_device *dev) ...@@ -300,7 +305,8 @@ static int vrf_rt6_create(struct net_device *dev)
rt6->rt6i_table = rt6i_table; rt6->rt6i_table = rt6i_table;
rt6->dst.output = vrf_output6; rt6->dst.output = vrf_output6;
vrf->rt6 = rt6; rcu_assign_pointer(vrf->rt6, rt6);
rc = 0; rc = 0;
out: out:
return rc; return rc;
...@@ -374,29 +380,35 @@ static int vrf_output(struct net *net, struct sock *sk, struct sk_buff *skb) ...@@ -374,29 +380,35 @@ static int vrf_output(struct net *net, struct sock *sk, struct sk_buff *skb)
!(IPCB(skb)->flags & IPSKB_REROUTED)); !(IPCB(skb)->flags & IPSKB_REROUTED));
} }
/* holding rtnl */
static void vrf_rtable_release(struct net_vrf *vrf) static void vrf_rtable_release(struct net_vrf *vrf)
{ {
struct dst_entry *dst = (struct dst_entry *)vrf->rth; struct rtable *rth = rtnl_dereference(vrf->rth);
rcu_assign_pointer(vrf->rth, NULL);
dst_release(dst); if (rth)
vrf->rth = NULL; dst_release(&rth->dst);
} }
static struct rtable *vrf_rtable_create(struct net_device *dev) static int vrf_rtable_create(struct net_device *dev)
{ {
struct net_vrf *vrf = netdev_priv(dev); struct net_vrf *vrf = netdev_priv(dev);
struct rtable *rth; struct rtable *rth;
if (!fib_new_table(dev_net(dev), vrf->tb_id)) if (!fib_new_table(dev_net(dev), vrf->tb_id))
return NULL; return -ENOMEM;
rth = rt_dst_alloc(dev, 0, RTN_UNICAST, 1, 1, 0); rth = rt_dst_alloc(dev, 0, RTN_UNICAST, 1, 1, 0);
if (rth) { if (!rth)
rth->dst.output = vrf_output; return -ENOMEM;
rth->rt_table_id = vrf->tb_id;
}
return rth; rth->dst.output = vrf_output;
rth->rt_table_id = vrf->tb_id;
rcu_assign_pointer(vrf->rth, rth);
return 0;
} }
/**************************** device handling ********************/ /**************************** device handling ********************/
...@@ -484,8 +496,7 @@ static int vrf_dev_init(struct net_device *dev) ...@@ -484,8 +496,7 @@ static int vrf_dev_init(struct net_device *dev)
goto out_nomem; goto out_nomem;
/* create the default dst which points back to us */ /* create the default dst which points back to us */
vrf->rth = vrf_rtable_create(dev); if (vrf_rtable_create(dev) != 0)
if (!vrf->rth)
goto out_stats; goto out_stats;
if (vrf_rt6_create(dev) != 0) if (vrf_rt6_create(dev) != 0)
...@@ -528,8 +539,13 @@ static struct rtable *vrf_get_rtable(const struct net_device *dev, ...@@ -528,8 +539,13 @@ static struct rtable *vrf_get_rtable(const struct net_device *dev,
if (!(fl4->flowi4_flags & FLOWI_FLAG_L3MDEV_SRC)) { if (!(fl4->flowi4_flags & FLOWI_FLAG_L3MDEV_SRC)) {
struct net_vrf *vrf = netdev_priv(dev); struct net_vrf *vrf = netdev_priv(dev);
rth = vrf->rth; rcu_read_lock();
dst_hold(&rth->dst);
rth = rcu_dereference(vrf->rth);
if (likely(rth))
dst_hold(&rth->dst);
rcu_read_unlock();
} }
return rth; return rth;
...@@ -665,16 +681,24 @@ static struct sk_buff *vrf_l3_rcv(struct net_device *vrf_dev, ...@@ -665,16 +681,24 @@ static struct sk_buff *vrf_l3_rcv(struct net_device *vrf_dev,
static struct dst_entry *vrf_get_rt6_dst(const struct net_device *dev, static struct dst_entry *vrf_get_rt6_dst(const struct net_device *dev,
const struct flowi6 *fl6) const struct flowi6 *fl6)
{ {
struct rt6_info *rt = NULL; struct dst_entry *dst = NULL;
if (!(fl6->flowi6_flags & FLOWI_FLAG_L3MDEV_SRC)) { if (!(fl6->flowi6_flags & FLOWI_FLAG_L3MDEV_SRC)) {
struct net_vrf *vrf = netdev_priv(dev); struct net_vrf *vrf = netdev_priv(dev);
struct rt6_info *rt;
rcu_read_lock();
rt = rcu_dereference(vrf->rt6);
if (likely(rt)) {
dst = &rt->dst;
dst_hold(dst);
}
rt = vrf->rt6; rcu_read_unlock();
dst_hold(&rt->dst);
} }
return (struct dst_entry *)rt; return dst;
} }
#endif #endif
......
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