lkml.org 
[lkml]   [2021]   [Jun]   [21]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[PATCH 5.12 164/178] net: bridge: fix vlan tunnel dst null pointer dereference
    Date
    From: Nikolay Aleksandrov <nikolay@nvidia.com>

    commit 58e2071742e38f29f051b709a5cca014ba51166f upstream.

    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: 11538d039ac6 ("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>
    Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
    ---
    net/bridge/br_private.h | 4 ++--
    net/bridge/br_vlan_tunnel.c | 38 ++++++++++++++++++++++++--------------
    2 files changed, 26 insertions(+), 16 deletions(-)

    --- a/net/bridge/br_private.h
    +++ b/net/bridge/br_private.h
    @@ -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 */
    --- a/net/bridge/br_vlan_tunnel.c
    +++ b/net/bridge/br_vlan_tunnel.c
    @@ -41,26 +41,33 @@ static struct net_bridge_vlan *br_vlan_t
    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,
    @@ -69,8 +76,8 @@ static int __vlan_tunnel_info_add(struct
    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);
    @@ -79,9 +86,7 @@ static int __vlan_tunnel_info_add(struct

    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;
    }
    @@ -182,12 +187,15 @@ int br_handle_ingress_vlan_tunnel(struct
    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);
    @@ -195,7 +203,9 @@ int br_handle_egress_vlan_tunnel(struct
    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;
    }

    \
     
     \ /
      Last update: 2021-06-21 18:51    [W:4.295 / U:0.072 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site