On Thu 03 Oct 2019 at 02:14, John Hurley <john.hur...@netronome.com> wrote:
> Flower has rule HW offload functions available that drivers can choose to
> register for. For the deletion case, these are triggered after filters
> have been removed from lookup tables both at the flower level, and the
> higher cls_api level. With flower running without RTNL locking, this can
> lead to races where HW offload messages get out of order.
>
> Ensure HW offloads stay in line with the kernel tables by triggering
> the sending of messages before the kernel processing is completed. For
> destroyed tcf_protos, do this at the new pre_destroy hook. Similarly, if
> a filter is being added, check that it is not concurrently being deleted
> before offloading to hw, rather than the current approach of offloading,
> then checking and reversing the offload if required.
>
> Fixes: 1d965c4def07 ("Refactor flower classifier to remove dependency on rtnl 
> lock")
> Fixes: 272ffaadeb3e ("net: sched: flower: handle concurrent tcf proto 
> deletion")
> Signed-off-by: John Hurley <john.hur...@netronome.com>
> Reported-by: Louis Peens <louis.pe...@netronome.com>
> ---
>  net/sched/cls_flower.c | 55 
> +++++++++++++++++++++++++++-----------------------
>  1 file changed, 30 insertions(+), 25 deletions(-)
>
> diff --git a/net/sched/cls_flower.c b/net/sched/cls_flower.c
> index 74221e3..3ac47b5 100644
> --- a/net/sched/cls_flower.c
> +++ b/net/sched/cls_flower.c
> @@ -513,13 +513,16 @@ static struct cls_fl_filter *__fl_get(struct 
> cls_fl_head *head, u32 handle)
>  }
>  
>  static int __fl_delete(struct tcf_proto *tp, struct cls_fl_filter *f,
> -                    bool *last, bool rtnl_held,
> +                    bool *last, bool rtnl_held, bool do_hw,
>                      struct netlink_ext_ack *extack)
>  {
>       struct cls_fl_head *head = fl_head_dereference(tp);
>  
>       *last = false;
>  
> +     if (do_hw && !tc_skip_hw(f->flags))
> +             fl_hw_destroy_filter(tp, f, rtnl_held, extack);
> +
>       spin_lock(&tp->lock);
>       if (f->deleted) {
>               spin_unlock(&tp->lock);
> @@ -534,8 +537,6 @@ static int __fl_delete(struct tcf_proto *tp, struct 
> cls_fl_filter *f,
>       spin_unlock(&tp->lock);
>  
>       *last = fl_mask_put(head, f->mask);
> -     if (!tc_skip_hw(f->flags))
> -             fl_hw_destroy_filter(tp, f, rtnl_held, extack);
>       tcf_unbind_filter(tp, &f->res);
>       __fl_put(f);
>  
> @@ -563,7 +564,7 @@ static void fl_destroy(struct tcf_proto *tp, bool 
> rtnl_held,
>  
>       list_for_each_entry_safe(mask, next_mask, &head->masks, list) {
>               list_for_each_entry_safe(f, next, &mask->filters, list) {
> -                     __fl_delete(tp, f, &last, rtnl_held, extack);
> +                     __fl_delete(tp, f, &last, rtnl_held, false, extack);
>                       if (last)
>                               break;
>               }
> @@ -574,6 +575,19 @@ static void fl_destroy(struct tcf_proto *tp, bool 
> rtnl_held,
>       tcf_queue_work(&head->rwork, fl_destroy_sleepable);
>  }
>  
> +static void fl_pre_destroy(struct tcf_proto *tp, bool rtnl_held,
> +                        struct netlink_ext_ack *extack)
> +{
> +     struct cls_fl_head *head = fl_head_dereference(tp);
> +     struct fl_flow_mask *mask, *next_mask;
> +     struct cls_fl_filter *f, *next;
> +
> +     list_for_each_entry_safe(mask, next_mask, &head->masks, list)
> +             list_for_each_entry_safe(f, next, &mask->filters, list)
> +                     if (!tc_skip_hw(f->flags))
> +                             fl_hw_destroy_filter(tp, f, rtnl_held, extack);
> +}
> +
>  static void fl_put(struct tcf_proto *tp, void *arg)
>  {
>       struct cls_fl_filter *f = arg;
> @@ -1588,6 +1602,13 @@ static int fl_change(struct net *net, struct sk_buff 
> *in_skb,
>       if (err)
>               goto errout_mask;
>  
> +     spin_lock(&tp->lock);
> +     if (tp->deleting || (fold && fold->deleted)) {
> +             err = -EAGAIN;
> +             goto errout_lock;
> +     }
> +     spin_unlock(&tp->lock);
> +

But what if one of these flag are set after this block? It would be
possible to insert dangling filters on tp that is being deleted, or
double list_replace_rcu() and idr replace() if same filter is replaced
concurrently, etc.

>       if (!tc_skip_hw(fnew->flags)) {
>               err = fl_hw_replace_filter(tp, fnew, rtnl_held, extack);
>               if (err)
> @@ -1598,22 +1619,7 @@ static int fl_change(struct net *net, struct sk_buff 
> *in_skb,
>               fnew->flags |= TCA_CLS_FLAGS_NOT_IN_HW;
>  
>       spin_lock(&tp->lock);
> -
> -     /* tp was deleted concurrently. -EAGAIN will cause caller to lookup
> -      * proto again or create new one, if necessary.
> -      */
> -     if (tp->deleting) {
> -             err = -EAGAIN;
> -             goto errout_hw;
> -     }
> -
>       if (fold) {
> -             /* Fold filter was deleted concurrently. Retry lookup. */
> -             if (fold->deleted) {
> -                     err = -EAGAIN;
> -                     goto errout_hw;
> -             }
> -
>               fnew->handle = handle;
>  
>               if (!in_ht) {
> @@ -1624,7 +1630,7 @@ static int fl_change(struct net *net, struct sk_buff 
> *in_skb,
>                                                    &fnew->ht_node,
>                                                    params);
>                       if (err)
> -                             goto errout_hw;
> +                             goto errout_lock;
>                       in_ht = true;
>               }
>  
> @@ -1667,7 +1673,7 @@ static int fl_change(struct net *net, struct sk_buff 
> *in_skb,
>                                           INT_MAX, GFP_ATOMIC);
>               }
>               if (err)
> -                     goto errout_hw;
> +                     goto errout_lock;
>  
>               refcount_inc(&fnew->refcnt);
>               fnew->handle = handle;
> @@ -1683,11 +1689,9 @@ static int fl_change(struct net *net, struct sk_buff 
> *in_skb,
>  
>  errout_ht:
>       spin_lock(&tp->lock);
> -errout_hw:
> +errout_lock:
>       fnew->deleted = true;
>       spin_unlock(&tp->lock);
> -     if (!tc_skip_hw(fnew->flags))
> -             fl_hw_destroy_filter(tp, fnew, rtnl_held, NULL);
>       if (in_ht)
>               rhashtable_remove_fast(&fnew->mask->ht, &fnew->ht_node,
>                                      fnew->mask->filter_ht_params);
> @@ -1713,7 +1717,7 @@ static int fl_delete(struct tcf_proto *tp, void *arg, 
> bool *last,
>       bool last_on_mask;
>       int err = 0;
>  
> -     err = __fl_delete(tp, f, &last_on_mask, rtnl_held, extack);
> +     err = __fl_delete(tp, f, &last_on_mask, rtnl_held, true, extack);
>       *last = list_empty(&head->masks);
>       __fl_put(f);
>  
> @@ -2509,6 +2513,7 @@ static struct tcf_proto_ops cls_fl_ops __read_mostly = {
>       .kind           = "flower",
>       .classify       = fl_classify,
>       .init           = fl_init,
> +     .pre_destroy    = fl_pre_destroy,
>       .destroy        = fl_destroy,
>       .get            = fl_get,
>       .put            = fl_put,

Reply via email to