Commit 275c44aa authored by Davide Caratti's avatar Davide Caratti Committed by David S. Miller
Browse files

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: 6676d5e4 ("net: sched: set dedicated tcf_walker flag when tp is empty")
Signed-off-by: default avatarDavide Caratti <dcaratti@redhat.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 615f22f5
Loading
Loading
Loading
Loading
+25 −0
Original line number Original line Diff line number Diff line
@@ -1108,10 +1108,33 @@ erridr:
	return err;
	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,
static void u32_walk(struct tcf_proto *tp, struct tcf_walker *arg,
		     bool rtnl_held)
		     bool rtnl_held)
{
{
	struct tc_u_common *tp_c = tp->data;
	struct tc_u_common *tp_c = tp->data;
	bool non_root_ht = false;
	struct tc_u_hnode *ht;
	struct tc_u_hnode *ht;
	struct tc_u_knode *n;
	struct tc_u_knode *n;
	unsigned int h;
	unsigned int h;
@@ -1124,6 +1147,8 @@ static void u32_walk(struct tcf_proto *tp, struct tcf_walker *arg,
	     ht = rtnl_dereference(ht->next)) {
	     ht = rtnl_dereference(ht->next)) {
		if (ht->prio != tp->prio)
		if (ht->prio != tp->prio)
			continue;
			continue;
		if (u32_hnode_empty(ht, &non_root_ht))
			return;
		if (arg->count >= arg->skip) {
		if (arg->count >= arg->skip) {
			if (arg->fn(tp, ht, arg) < 0) {
			if (arg->fn(tp, ht, arg) < 0) {
				arg->stop = 1;
				arg->stop = 1;