lkml.org 
[lkml]   [2022]   [Jun]   [13]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[PATCH 5.4 017/411] ipv6: fix locking issues with loops over idev->addr_list
    Date
    From: Niels Dossche <dossche.niels@gmail.com>

    [ Upstream commit 51454ea42c1ab4e0c2828bb0d4d53957976980de ]

    idev->addr_list needs to be protected by idev->lock. However, it is not
    always possible to do so while iterating and performing actions on
    inet6_ifaddr instances. For example, multiple functions (like
    addrconf_{join,leave}_anycast) eventually call down to other functions
    that acquire the idev->lock. The current code temporarily unlocked the
    idev->lock during the loops, which can cause race conditions. Moving the
    locks up is also not an appropriate solution as the ordering of lock
    acquisition will be inconsistent with for example mc_lock.

    This solution adds an additional field to inet6_ifaddr that is used
    to temporarily add the instances to a temporary list while holding
    idev->lock. The temporary list can then be traversed without holding
    idev->lock. This change was done in two places. In addrconf_ifdown, the
    list_for_each_entry_safe variant of the list loop is also no longer
    necessary as there is no deletion within that specific loop.

    Suggested-by: Paolo Abeni <pabeni@redhat.com>
    Signed-off-by: Niels Dossche <dossche.niels@gmail.com>
    Acked-by: Paolo Abeni <pabeni@redhat.com>
    Link: https://lore.kernel.org/r/20220403231523.45843-1-dossche.niels@gmail.com
    Signed-off-by: Jakub Kicinski <kuba@kernel.org>
    Signed-off-by: Sasha Levin <sashal@kernel.org>
    ---
    include/net/if_inet6.h | 8 ++++++++
    net/ipv6/addrconf.c | 30 ++++++++++++++++++++++++------
    2 files changed, 32 insertions(+), 6 deletions(-)

    diff --git a/include/net/if_inet6.h b/include/net/if_inet6.h
    index a01981d7108f..f6d614926e9e 100644
    --- a/include/net/if_inet6.h
    +++ b/include/net/if_inet6.h
    @@ -64,6 +64,14 @@ struct inet6_ifaddr {

    struct hlist_node addr_lst;
    struct list_head if_list;
    + /*
    + * Used to safely traverse idev->addr_list in process context
    + * if the idev->lock needed to protect idev->addr_list cannot be held.
    + * In that case, add the items to this list temporarily and iterate
    + * without holding idev->lock.
    + * See addrconf_ifdown and dev_forward_change.
    + */
    + struct list_head if_list_aux;

    struct list_head tmp_list;
    struct inet6_ifaddr *ifpub;
    diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
    index 92b32d131e1c..efea88fb3cd5 100644
    --- a/net/ipv6/addrconf.c
    +++ b/net/ipv6/addrconf.c
    @@ -789,6 +789,7 @@ static void dev_forward_change(struct inet6_dev *idev)
    {
    struct net_device *dev;
    struct inet6_ifaddr *ifa;
    + LIST_HEAD(tmp_addr_list);

    if (!idev)
    return;
    @@ -807,14 +808,24 @@ static void dev_forward_change(struct inet6_dev *idev)
    }
    }

    + read_lock_bh(&idev->lock);
    list_for_each_entry(ifa, &idev->addr_list, if_list) {
    if (ifa->flags&IFA_F_TENTATIVE)
    continue;
    + list_add_tail(&ifa->if_list_aux, &tmp_addr_list);
    + }
    + read_unlock_bh(&idev->lock);
    +
    + while (!list_empty(&tmp_addr_list)) {
    + ifa = list_first_entry(&tmp_addr_list,
    + struct inet6_ifaddr, if_list_aux);
    + list_del(&ifa->if_list_aux);
    if (idev->cnf.forwarding)
    addrconf_join_anycast(ifa);
    else
    addrconf_leave_anycast(ifa);
    }
    +
    inet6_netconf_notify_devconf(dev_net(dev), RTM_NEWNETCONF,
    NETCONFA_FORWARDING,
    dev->ifindex, &idev->cnf);
    @@ -3713,7 +3724,8 @@ static int addrconf_ifdown(struct net_device *dev, int how)
    unsigned long event = how ? NETDEV_UNREGISTER : NETDEV_DOWN;
    struct net *net = dev_net(dev);
    struct inet6_dev *idev;
    - struct inet6_ifaddr *ifa, *tmp;
    + struct inet6_ifaddr *ifa;
    + LIST_HEAD(tmp_addr_list);
    bool keep_addr = false;
    bool was_ready;
    int state, i;
    @@ -3805,16 +3817,23 @@ static int addrconf_ifdown(struct net_device *dev, int how)
    write_lock_bh(&idev->lock);
    }

    - list_for_each_entry_safe(ifa, tmp, &idev->addr_list, if_list) {
    + list_for_each_entry(ifa, &idev->addr_list, if_list)
    + list_add_tail(&ifa->if_list_aux, &tmp_addr_list);
    + write_unlock_bh(&idev->lock);
    +
    + while (!list_empty(&tmp_addr_list)) {
    struct fib6_info *rt = NULL;
    bool keep;

    + ifa = list_first_entry(&tmp_addr_list,
    + struct inet6_ifaddr, if_list_aux);
    + list_del(&ifa->if_list_aux);
    +
    addrconf_del_dad_work(ifa);

    keep = keep_addr && (ifa->flags & IFA_F_PERMANENT) &&
    !addr_is_local(&ifa->addr);

    - write_unlock_bh(&idev->lock);
    spin_lock_bh(&ifa->lock);

    if (keep) {
    @@ -3845,15 +3864,14 @@ static int addrconf_ifdown(struct net_device *dev, int how)
    addrconf_leave_solict(ifa->idev, &ifa->addr);
    }

    - write_lock_bh(&idev->lock);
    if (!keep) {
    + write_lock_bh(&idev->lock);
    list_del_rcu(&ifa->if_list);
    + write_unlock_bh(&idev->lock);
    in6_ifa_put(ifa);
    }
    }

    - write_unlock_bh(&idev->lock);
    -
    /* Step 5: Discard anycast and multicast list */
    if (how) {
    ipv6_ac_destroy_dev(idev);
    --
    2.35.1


    \
     
     \ /
      Last update: 2022-06-13 13:06    [W:4.063 / U:0.276 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site