net_sched: move the empty tp check from ->destroy() to ->delete()
We could have a race condition where in ->classify() path we dereference tp->root and meanwhile a parallel ->destroy() makes it a NULL. Daniel cured this bug in commitd936377414
("net, sched: respect rcu grace period on cls destruction"). This happens when ->destroy() is called for deleting a filter to check if we are the last one in tp, this tp is still linked and visible at that time. The root cause of this problem is the semantic of ->destroy(), it does two things (for non-force case): 1) check if tp is empty 2) if tp is empty we could really destroy it and its caller, if cares, needs to check its return value to see if it is really destroyed. Therefore we can't unlink tp unless we know it is empty. As suggested by Daniel, we could actually move the test logic to ->delete() so that we can safely unlink tp after ->delete() tells us the last one is just deleted and before ->destroy(). Fixes:1e052be69d
("net_sched: destroy proto tp when all filters are gone") Cc: Roi Dayan <roid@mellanox.com> Cc: Daniel Borkmann <daniel@iogearbox.net> Cc: John Fastabend <john.fastabend@gmail.com> Cc: Jamal Hadi Salim <jhs@mojatatu.com> Signed-off-by: Cong Wang <xiyou.wangcong@gmail.com> Acked-by: Daniel Borkmann <daniel@iogearbox.net> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:

committed by
David S. Miller

parent
b1d9fc41aa
commit
763dbf6328
@@ -585,37 +585,13 @@ static bool ht_empty(struct tc_u_hnode *ht)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool u32_destroy(struct tcf_proto *tp, bool force)
|
||||
static void u32_destroy(struct tcf_proto *tp)
|
||||
{
|
||||
struct tc_u_common *tp_c = tp->data;
|
||||
struct tc_u_hnode *root_ht = rtnl_dereference(tp->root);
|
||||
|
||||
WARN_ON(root_ht == NULL);
|
||||
|
||||
if (!force) {
|
||||
if (root_ht) {
|
||||
if (root_ht->refcnt > 1)
|
||||
return false;
|
||||
if (root_ht->refcnt == 1) {
|
||||
if (!ht_empty(root_ht))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (tp_c->refcnt > 1)
|
||||
return false;
|
||||
|
||||
if (tp_c->refcnt == 1) {
|
||||
struct tc_u_hnode *ht;
|
||||
|
||||
for (ht = rtnl_dereference(tp_c->hlist);
|
||||
ht;
|
||||
ht = rtnl_dereference(ht->next))
|
||||
if (!ht_empty(ht))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (root_ht && --root_ht->refcnt == 0)
|
||||
u32_destroy_hnode(tp, root_ht);
|
||||
|
||||
@@ -640,20 +616,22 @@ static bool u32_destroy(struct tcf_proto *tp, bool force)
|
||||
}
|
||||
|
||||
tp->data = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int u32_delete(struct tcf_proto *tp, unsigned long arg)
|
||||
static int u32_delete(struct tcf_proto *tp, unsigned long arg, bool *last)
|
||||
{
|
||||
struct tc_u_hnode *ht = (struct tc_u_hnode *)arg;
|
||||
struct tc_u_hnode *root_ht = rtnl_dereference(tp->root);
|
||||
struct tc_u_common *tp_c = tp->data;
|
||||
int ret = 0;
|
||||
|
||||
if (ht == NULL)
|
||||
return 0;
|
||||
goto out;
|
||||
|
||||
if (TC_U32_KEY(ht->handle)) {
|
||||
u32_remove_hw_knode(tp, ht->handle);
|
||||
return u32_delete_key(tp, (struct tc_u_knode *)ht);
|
||||
ret = u32_delete_key(tp, (struct tc_u_knode *)ht);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (root_ht == ht)
|
||||
@@ -666,7 +644,40 @@ static int u32_delete(struct tcf_proto *tp, unsigned long arg)
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
return 0;
|
||||
out:
|
||||
*last = true;
|
||||
if (root_ht) {
|
||||
if (root_ht->refcnt > 1) {
|
||||
*last = false;
|
||||
goto ret;
|
||||
}
|
||||
if (root_ht->refcnt == 1) {
|
||||
if (!ht_empty(root_ht)) {
|
||||
*last = false;
|
||||
goto ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tp_c->refcnt > 1) {
|
||||
*last = false;
|
||||
goto ret;
|
||||
}
|
||||
|
||||
if (tp_c->refcnt == 1) {
|
||||
struct tc_u_hnode *ht;
|
||||
|
||||
for (ht = rtnl_dereference(tp_c->hlist);
|
||||
ht;
|
||||
ht = rtnl_dereference(ht->next))
|
||||
if (!ht_empty(ht)) {
|
||||
*last = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ret:
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define NR_U32_NODE (1<<12)
|
||||
|
Reference in New Issue
Block a user