Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit dcd62aa6 authored by Cong Wang's avatar Cong Wang Committed by Greg Kroah-Hartman
Browse files

net_sched: fix a memory leak in cls_tcindex



[ Upstream commit 033b228e7f26b29ae37f8bfa1bc6b209a5365e9f ]

When tcindex_destroy() destroys all the filter results in
the perfect hash table, it invokes the walker to delete
each of them. However, results with class==0 are skipped
in either tcindex_walk() or tcindex_delete(), which causes
a memory leak reported by kmemleak.

This patch fixes it by skipping the walker and directly
deleting these filter results so we don't miss any filter
result.

As a result of this change, we have to initialize exts->net
properly in tcindex_alloc_perfect_hash(). For net-next, we
need to consider whether we should initialize ->net in
tcf_exts_init() instead, before that just directly test
CONFIG_NET_CLS_ACT=y.

Cc: Jamal Hadi Salim <jhs@mojatatu.com>
Cc: Jiri Pirko <jiri@resnulli.us>
Signed-off-by: default avatarCong Wang <xiyou.wangcong@gmail.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 056a1798
Loading
Loading
Loading
Loading
+30 −16
Original line number Original line Diff line number Diff line
@@ -221,14 +221,6 @@ static int tcindex_delete(struct tcf_proto *tp, void *arg, bool *last,
	return 0;
	return 0;
}
}


static int tcindex_destroy_element(struct tcf_proto *tp,
				   void *arg, struct tcf_walker *walker)
{
	bool last;

	return tcindex_delete(tp, arg, &last, NULL);
}

static void tcindex_destroy_work(struct work_struct *work)
static void tcindex_destroy_work(struct work_struct *work)
{
{
	struct tcindex_data *p = container_of(to_rcu_work(work),
	struct tcindex_data *p = container_of(to_rcu_work(work),
@@ -279,7 +271,7 @@ static void tcindex_free_perfect_hash(struct tcindex_data *cp)
	kfree(cp->perfect);
	kfree(cp->perfect);
}
}


static int tcindex_alloc_perfect_hash(struct tcindex_data *cp)
static int tcindex_alloc_perfect_hash(struct net *net, struct tcindex_data *cp)
{
{
	int i, err = 0;
	int i, err = 0;


@@ -293,6 +285,9 @@ static int tcindex_alloc_perfect_hash(struct tcindex_data *cp)
				    TCA_TCINDEX_ACT, TCA_TCINDEX_POLICE);
				    TCA_TCINDEX_ACT, TCA_TCINDEX_POLICE);
		if (err < 0)
		if (err < 0)
			goto errout;
			goto errout;
#ifdef CONFIG_NET_CLS_ACT
		cp->perfect[i].exts.net = net;
#endif
	}
	}


	return 0;
	return 0;
@@ -341,7 +336,7 @@ tcindex_set_parms(struct net *net, struct tcf_proto *tp, unsigned long base,
	if (p->perfect) {
	if (p->perfect) {
		int i;
		int i;


		if (tcindex_alloc_perfect_hash(cp) < 0)
		if (tcindex_alloc_perfect_hash(net, cp) < 0)
			goto errout;
			goto errout;
		for (i = 0; i < cp->hash; i++)
		for (i = 0; i < cp->hash; i++)
			cp->perfect[i].res = p->perfect[i].res;
			cp->perfect[i].res = p->perfect[i].res;
@@ -410,7 +405,7 @@ tcindex_set_parms(struct net *net, struct tcf_proto *tp, unsigned long base,
	err = -ENOMEM;
	err = -ENOMEM;
	if (!cp->perfect && !cp->h) {
	if (!cp->perfect && !cp->h) {
		if (valid_perfect_hash(cp)) {
		if (valid_perfect_hash(cp)) {
			if (tcindex_alloc_perfect_hash(cp) < 0)
			if (tcindex_alloc_perfect_hash(net, cp) < 0)
				goto errout_alloc;
				goto errout_alloc;
			balloc = 1;
			balloc = 1;
		} else {
		} else {
@@ -566,13 +561,32 @@ static void tcindex_destroy(struct tcf_proto *tp,
			    struct netlink_ext_ack *extack)
			    struct netlink_ext_ack *extack)
{
{
	struct tcindex_data *p = rtnl_dereference(tp->root);
	struct tcindex_data *p = rtnl_dereference(tp->root);
	struct tcf_walker walker;
	int i;


	pr_debug("tcindex_destroy(tp %p),p %p\n", tp, p);
	pr_debug("tcindex_destroy(tp %p),p %p\n", tp, p);
	walker.count = 0;

	walker.skip = 0;
	if (p->perfect) {
	walker.fn = tcindex_destroy_element;
		for (i = 0; i < p->hash; i++) {
	tcindex_walk(tp, &walker);
			struct tcindex_filter_result *r = p->perfect + i;

			tcf_unbind_filter(tp, &r->res);
			if (tcf_exts_get_net(&r->exts))
				tcf_queue_work(&r->rwork,
					       tcindex_destroy_rexts_work);
			else
				__tcindex_destroy_rexts(r);
		}
	}

	for (i = 0; p->h && i < p->hash; i++) {
		struct tcindex_filter *f, *next;
		bool last;

		for (f = rtnl_dereference(p->h[i]); f; f = next) {
			next = rtnl_dereference(f->next);
			tcindex_delete(tp, &f->result, &last, NULL);
		}
	}


	tcf_queue_work(&p->rwork, tcindex_destroy_work);
	tcf_queue_work(&p->rwork, tcindex_destroy_work);
}
}