From: Davide Caratti Date: Tue, 17 Dec 2019 23:00:04 +0000 (+0100) Subject: net/sched: cls_u32: fix refcount leak in the error path of u32_change() X-Git-Tag: microblaze-v5.7-rc1~368^2~15^2~1 X-Git-Url: http://git.monstr.eu/?a=commitdiff_plain;h=275c44aa194b;p=linux-2.6-microblaze.git net/sched: cls_u32: fix refcount leak in the error path of u32_change() when users replace cls_u32 filters with new ones having wrong parameters, so that u32_change() fails to validate them, the kernel doesn't roll-back correctly, and leaves semi-configured rules. Fix this in u32_walk(), avoiding a call to the walker function on filters that don't have a match rule connected. The side effect is, these "empty" filters are not even dumped when present; but that shouldn't be a problem as long as we are restoring the original behaviour, where semi-configured filters were not even added in the error path of u32_change(). Fixes: 6676d5e416ee ("net: sched: set dedicated tcf_walker flag when tp is empty") Signed-off-by: Davide Caratti Signed-off-by: David S. Miller --- diff --git a/net/sched/cls_u32.c b/net/sched/cls_u32.c index a0e6fac613de..66c6bcec16cb 100644 --- a/net/sched/cls_u32.c +++ b/net/sched/cls_u32.c @@ -1108,10 +1108,33 @@ erridr: return err; } +static bool u32_hnode_empty(struct tc_u_hnode *ht, bool *non_root_ht) +{ + int i; + + if (!ht) + return true; + if (!ht->is_root) { + *non_root_ht = true; + return false; + } + if (*non_root_ht) + return false; + if (ht->refcnt < 2) + return true; + + for (i = 0; i <= ht->divisor; i++) { + if (rtnl_dereference(ht->ht[i])) + return false; + } + return true; +} + static void u32_walk(struct tcf_proto *tp, struct tcf_walker *arg, bool rtnl_held) { struct tc_u_common *tp_c = tp->data; + bool non_root_ht = false; struct tc_u_hnode *ht; struct tc_u_knode *n; unsigned int h; @@ -1124,6 +1147,8 @@ static void u32_walk(struct tcf_proto *tp, struct tcf_walker *arg, ht = rtnl_dereference(ht->next)) { if (ht->prio != tp->prio) continue; + if (u32_hnode_empty(ht, &non_root_ht)) + return; if (arg->count >= arg->skip) { if (arg->fn(tp, ht, arg) < 0) { arg->stop = 1;