Extend tcf_chain with 'flushing' flag. Use the flag to prevent insertion of new classifier instances when chain flushing is in progress in order to prevent resource leak when tcf_proto is created by unlocked users concurrently.
Return EAGAIN error from tcf_chain_tp_insert_unique() to restart tc_new_tfilter() and lookup the chain/proto again. Signed-off-by: Vlad Buslov <vla...@mellanox.com> Acked-by: Jiri Pirko <j...@mellanox.com> --- include/net/sch_generic.h | 1 + net/sched/cls_api.c | 35 ++++++++++++++++++++++++++--------- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/include/net/sch_generic.h b/include/net/sch_generic.h index 4809eca41f95..100368594524 100644 --- a/include/net/sch_generic.h +++ b/include/net/sch_generic.h @@ -353,6 +353,7 @@ struct tcf_chain { unsigned int refcnt; unsigned int action_refcnt; bool explicitly_created; + bool flushing; const struct tcf_proto_ops *tmplt_ops; void *tmplt_priv; }; diff --git a/net/sched/cls_api.c b/net/sched/cls_api.c index 3ce244fbfb4d..d4f763525412 100644 --- a/net/sched/cls_api.c +++ b/net/sched/cls_api.c @@ -483,9 +483,12 @@ static void __tcf_chain_put(struct tcf_chain *chain, bool by_act, spin_unlock(&block->lock); /* The last dropped non-action reference will trigger notification. */ - if (is_last && !by_act) + if (is_last && !by_act) { tc_chain_notify_delete(tmplt_ops, tmplt_priv, chain_index, block, NULL, 0, 0, false); + /* Last reference to chain, no need to lock. */ + chain->flushing = false; + } if (refcnt == 0) { tc_chain_tmplt_del(tmplt_ops, tmplt_priv); @@ -517,6 +520,7 @@ static void tcf_chain_flush(struct tcf_chain *chain) tp = tcf_chain_dereference(chain->filter_chain, chain); RCU_INIT_POINTER(chain->filter_chain, NULL); tcf_chain0_head_change(chain, NULL); + chain->flushing = true; spin_unlock(&chain->filter_chain_lock); while (tp) { @@ -1382,15 +1386,20 @@ static struct tcf_proto *tcf_chain_tp_prev(struct tcf_chain *chain, return tcf_chain_dereference(*chain_info->pprev, chain); } -static void tcf_chain_tp_insert(struct tcf_chain *chain, - struct tcf_chain_info *chain_info, - struct tcf_proto *tp) +static int tcf_chain_tp_insert(struct tcf_chain *chain, + struct tcf_chain_info *chain_info, + struct tcf_proto *tp) { + if (chain->flushing) + return -EAGAIN; + if (*chain_info->pprev == chain->filter_chain) tcf_chain0_head_change(chain, tp); tcf_proto_get(tp); RCU_INIT_POINTER(tp->next, tcf_chain_tp_prev(chain, chain_info)); rcu_assign_pointer(*chain_info->pprev, tp); + + return 0; } static void tcf_chain_tp_remove(struct tcf_chain *chain, @@ -1421,18 +1430,22 @@ static struct tcf_proto *tcf_chain_tp_insert_unique(struct tcf_chain *chain, { struct tcf_chain_info chain_info; struct tcf_proto *tp; + int err = 0; spin_lock(&chain->filter_chain_lock); tp = tcf_chain_tp_find(chain, &chain_info, protocol, prio, false); if (!tp) - tcf_chain_tp_insert(chain, &chain_info, tp_new); + err = tcf_chain_tp_insert(chain, &chain_info, tp_new); spin_unlock(&chain->filter_chain_lock); if (tp) { tcf_proto_destroy(tp_new, NULL); tp_new = tp; + } else if (err) { + tcf_proto_destroy(tp_new, NULL); + tp_new = ERR_PTR(err); } return tp_new; @@ -1743,11 +1756,15 @@ static int tc_new_tfilter(struct sk_buff *skb, struct nlmsghdr *n, tp = tcf_chain_tp_insert_unique(chain, tp_new, protocol, prio); - /* tp insert function can return another tp instance, if it was - * created concurrently. - */ - if (tp == tp_new) + if (IS_ERR(tp)) { + err = PTR_ERR(tp); + goto errout; + } else if (tp == tp_new) { + /* tp insert function can return another tp instance, if + * it was created concurrently. + */ tp_created = 1; + } } else { spin_unlock(&chain->filter_chain_lock); } -- 2.7.5