Skip to content

Commit

Permalink
net: bridge: fix vlan tunnel dst null pointer dereference
Browse files Browse the repository at this point in the history
This patch fixes a tunnel_dst null pointer dereference due to lockless
access in the tunnel egress path. When deleting a vlan tunnel the
tunnel_dst pointer is set to NULL without waiting a grace period (i.e.
while it's still usable) and packets egressing are dereferencing it
without checking. Use READ/WRITE_ONCE to annotate the lockless use of
tunnel_id, use RCU for accessing tunnel_dst and make sure it is read
only once and checked in the egress path. The dst is already properly RCU
protected so we don't need to do anything fancy than to make sure
tunnel_id and tunnel_dst are read only once and checked in the egress path.

Cc: stable@vger.kernel.org
Fixes: 11538d0 ("bridge: vlan dst_metadata hooks in ingress and egress paths")
Signed-off-by: Nikolay Aleksandrov <nikolay@nvidia.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
Nikolay Aleksandrov authored and davem330 committed Jun 10, 2021
1 parent 9d44fa3 commit 58e2071
Show file tree
Hide file tree
Showing 2 changed files with 26 additions and 16 deletions.
4 changes: 2 additions & 2 deletions net/bridge/br_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ struct bridge_mcast_stats {
#endif

struct br_tunnel_info {
__be64 tunnel_id;
struct metadata_dst *tunnel_dst;
__be64 tunnel_id;
struct metadata_dst __rcu *tunnel_dst;
};

/* private vlan flags */
Expand Down
38 changes: 24 additions & 14 deletions net/bridge/br_vlan_tunnel.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,26 +41,33 @@ static struct net_bridge_vlan *br_vlan_tunnel_lookup(struct rhashtable *tbl,
br_vlan_tunnel_rht_params);
}

static void vlan_tunnel_info_release(struct net_bridge_vlan *vlan)
{
struct metadata_dst *tdst = rtnl_dereference(vlan->tinfo.tunnel_dst);

WRITE_ONCE(vlan->tinfo.tunnel_id, 0);
RCU_INIT_POINTER(vlan->tinfo.tunnel_dst, NULL);
dst_release(&tdst->dst);
}

void vlan_tunnel_info_del(struct net_bridge_vlan_group *vg,
struct net_bridge_vlan *vlan)
{
if (!vlan->tinfo.tunnel_dst)
if (!rcu_access_pointer(vlan->tinfo.tunnel_dst))
return;
rhashtable_remove_fast(&vg->tunnel_hash, &vlan->tnode,
br_vlan_tunnel_rht_params);
vlan->tinfo.tunnel_id = 0;
dst_release(&vlan->tinfo.tunnel_dst->dst);
vlan->tinfo.tunnel_dst = NULL;
vlan_tunnel_info_release(vlan);
}

static int __vlan_tunnel_info_add(struct net_bridge_vlan_group *vg,
struct net_bridge_vlan *vlan, u32 tun_id)
{
struct metadata_dst *metadata = NULL;
struct metadata_dst *metadata = rtnl_dereference(vlan->tinfo.tunnel_dst);
__be64 key = key32_to_tunnel_id(cpu_to_be32(tun_id));
int err;

if (vlan->tinfo.tunnel_dst)
if (metadata)
return -EEXIST;

metadata = __ip_tun_set_dst(0, 0, 0, 0, 0, TUNNEL_KEY,
Expand All @@ -69,8 +76,8 @@ static int __vlan_tunnel_info_add(struct net_bridge_vlan_group *vg,
return -EINVAL;

metadata->u.tun_info.mode |= IP_TUNNEL_INFO_TX | IP_TUNNEL_INFO_BRIDGE;
vlan->tinfo.tunnel_dst = metadata;
vlan->tinfo.tunnel_id = key;
rcu_assign_pointer(vlan->tinfo.tunnel_dst, metadata);
WRITE_ONCE(vlan->tinfo.tunnel_id, key);

err = rhashtable_lookup_insert_fast(&vg->tunnel_hash, &vlan->tnode,
br_vlan_tunnel_rht_params);
Expand All @@ -79,9 +86,7 @@ static int __vlan_tunnel_info_add(struct net_bridge_vlan_group *vg,

return 0;
out:
dst_release(&vlan->tinfo.tunnel_dst->dst);
vlan->tinfo.tunnel_dst = NULL;
vlan->tinfo.tunnel_id = 0;
vlan_tunnel_info_release(vlan);

return err;
}
Expand Down Expand Up @@ -182,20 +187,25 @@ int br_handle_ingress_vlan_tunnel(struct sk_buff *skb,
int br_handle_egress_vlan_tunnel(struct sk_buff *skb,
struct net_bridge_vlan *vlan)
{
struct metadata_dst *tunnel_dst;
__be64 tunnel_id;
int err;

if (!vlan || !vlan->tinfo.tunnel_id)
if (!vlan)
return 0;

if (unlikely(!skb_vlan_tag_present(skb)))
tunnel_id = READ_ONCE(vlan->tinfo.tunnel_id);
if (!tunnel_id || unlikely(!skb_vlan_tag_present(skb)))
return 0;

skb_dst_drop(skb);
err = skb_vlan_pop(skb);
if (err)
return err;

skb_dst_set(skb, dst_clone(&vlan->tinfo.tunnel_dst->dst));
tunnel_dst = rcu_dereference(vlan->tinfo.tunnel_dst);
if (tunnel_dst)
skb_dst_set(skb, dst_clone(&tunnel_dst->dst));

return 0;
}

0 comments on commit 58e2071

Please sign in to comment.