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

Commit 6237e86d authored by Liping Zhang's avatar Liping Zhang Committed by Greg Kroah-Hartman
Browse files

netfilter: nfnl_cthelper: fix a race when walk the nf_ct_helper_hash table




[ Upstream commit 83d90219a5df8d950855ce73229a97b63605c317 ]

The nf_ct_helper_hash table is protected by nf_ct_helper_mutex, while
nfct_helper operation is protected by nfnl_lock(NFNL_SUBSYS_CTHELPER).
So it's possible that one CPU is walking the nf_ct_helper_hash for
cthelper add/get/del, another cpu is doing nf_conntrack_helpers_unregister
at the same time. This is dangrous, and may cause use after free error.

Note, delete operation will flush all cthelpers added via nfnetlink, so
using rcu to do protect is not easy.

Now introduce a dummy list to record all the cthelpers added via
nfnetlink, then we can walk the dummy list instead of walking the
nf_ct_helper_hash. Also, keep nfnl_cthelper_dump_table unchanged, it
may be invoked without nfnl_lock(NFNL_SUBSYS_CTHELPER) held.

Signed-off-by: default avatarLiping Zhang <zlpnobody@gmail.com>
Signed-off-by: default avatarPablo Neira Ayuso <pablo@netfilter.org>
Signed-off-by: default avatarSasha Levin <alexander.levin@verizon.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent cba690fe
Loading
Loading
Loading
Loading
+81 −96
Original line number Original line Diff line number Diff line
@@ -32,6 +32,13 @@ MODULE_LICENSE("GPL");
MODULE_AUTHOR("Pablo Neira Ayuso <pablo@netfilter.org>");
MODULE_AUTHOR("Pablo Neira Ayuso <pablo@netfilter.org>");
MODULE_DESCRIPTION("nfnl_cthelper: User-space connection tracking helpers");
MODULE_DESCRIPTION("nfnl_cthelper: User-space connection tracking helpers");


struct nfnl_cthelper {
	struct list_head		list;
	struct nf_conntrack_helper	helper;
};

static LIST_HEAD(nfnl_cthelper_list);

static int
static int
nfnl_userspace_cthelper(struct sk_buff *skb, unsigned int protoff,
nfnl_userspace_cthelper(struct sk_buff *skb, unsigned int protoff,
			struct nf_conn *ct, enum ip_conntrack_info ctinfo)
			struct nf_conn *ct, enum ip_conntrack_info ctinfo)
@@ -205,14 +212,16 @@ nfnl_cthelper_create(const struct nlattr * const tb[],
		     struct nf_conntrack_tuple *tuple)
		     struct nf_conntrack_tuple *tuple)
{
{
	struct nf_conntrack_helper *helper;
	struct nf_conntrack_helper *helper;
	struct nfnl_cthelper *nfcth;
	int ret;
	int ret;


	if (!tb[NFCTH_TUPLE] || !tb[NFCTH_POLICY] || !tb[NFCTH_PRIV_DATA_LEN])
	if (!tb[NFCTH_TUPLE] || !tb[NFCTH_POLICY] || !tb[NFCTH_PRIV_DATA_LEN])
		return -EINVAL;
		return -EINVAL;


	helper = kzalloc(sizeof(struct nf_conntrack_helper), GFP_KERNEL);
	nfcth = kzalloc(sizeof(*nfcth), GFP_KERNEL);
	if (helper == NULL)
	if (nfcth == NULL)
		return -ENOMEM;
		return -ENOMEM;
	helper = &nfcth->helper;


	ret = nfnl_cthelper_parse_expect_policy(helper, tb[NFCTH_POLICY]);
	ret = nfnl_cthelper_parse_expect_policy(helper, tb[NFCTH_POLICY]);
	if (ret < 0)
	if (ret < 0)
@@ -249,11 +258,12 @@ nfnl_cthelper_create(const struct nlattr * const tb[],
	if (ret < 0)
	if (ret < 0)
		goto err2;
		goto err2;


	list_add_tail(&nfcth->list, &nfnl_cthelper_list);
	return 0;
	return 0;
err2:
err2:
	kfree(helper->expect_policy);
	kfree(helper->expect_policy);
err1:
err1:
	kfree(helper);
	kfree(nfcth);
	return ret;
	return ret;
}
}


@@ -379,7 +389,8 @@ nfnl_cthelper_new(struct sock *nfnl, struct sk_buff *skb,
	const char *helper_name;
	const char *helper_name;
	struct nf_conntrack_helper *cur, *helper = NULL;
	struct nf_conntrack_helper *cur, *helper = NULL;
	struct nf_conntrack_tuple tuple;
	struct nf_conntrack_tuple tuple;
	int ret = 0, i;
	struct nfnl_cthelper *nlcth;
	int ret = 0;


	if (!tb[NFCTH_NAME] || !tb[NFCTH_TUPLE])
	if (!tb[NFCTH_NAME] || !tb[NFCTH_TUPLE])
		return -EINVAL;
		return -EINVAL;
@@ -390,31 +401,22 @@ nfnl_cthelper_new(struct sock *nfnl, struct sk_buff *skb,
	if (ret < 0)
	if (ret < 0)
		return ret;
		return ret;


	rcu_read_lock();
	list_for_each_entry(nlcth, &nfnl_cthelper_list, list) {
	for (i = 0; i < nf_ct_helper_hsize && !helper; i++) {
		cur = &nlcth->helper;
		hlist_for_each_entry_rcu(cur, &nf_ct_helper_hash[i], hnode) {

			/* skip non-userspace conntrack helpers. */
			if (!(cur->flags & NF_CT_HELPER_F_USERSPACE))
				continue;


			if (strncmp(cur->name, helper_name,
		if (strncmp(cur->name, helper_name, NF_CT_HELPER_NAME_LEN))
					NF_CT_HELPER_NAME_LEN) != 0)
			continue;
			continue;


		if ((tuple.src.l3num != cur->tuple.src.l3num ||
		if ((tuple.src.l3num != cur->tuple.src.l3num ||
		     tuple.dst.protonum != cur->tuple.dst.protonum))
		     tuple.dst.protonum != cur->tuple.dst.protonum))
			continue;
			continue;


			if (nlh->nlmsg_flags & NLM_F_EXCL) {
		if (nlh->nlmsg_flags & NLM_F_EXCL)
				ret = -EEXIST;
			return -EEXIST;
				goto err;

			}
		helper = cur;
		helper = cur;
		break;
		break;
	}
	}
	}
	rcu_read_unlock();


	if (helper == NULL)
	if (helper == NULL)
		ret = nfnl_cthelper_create(tb, &tuple);
		ret = nfnl_cthelper_create(tb, &tuple);
@@ -422,9 +424,6 @@ nfnl_cthelper_new(struct sock *nfnl, struct sk_buff *skb,
		ret = nfnl_cthelper_update(tb, helper);
		ret = nfnl_cthelper_update(tb, helper);


	return ret;
	return ret;
err:
	rcu_read_unlock();
	return ret;
}
}


static int
static int
@@ -588,11 +587,12 @@ static int
nfnl_cthelper_get(struct sock *nfnl, struct sk_buff *skb,
nfnl_cthelper_get(struct sock *nfnl, struct sk_buff *skb,
		  const struct nlmsghdr *nlh, const struct nlattr * const tb[])
		  const struct nlmsghdr *nlh, const struct nlattr * const tb[])
{
{
	int ret = -ENOENT, i;
	int ret = -ENOENT;
	struct nf_conntrack_helper *cur;
	struct nf_conntrack_helper *cur;
	struct sk_buff *skb2;
	struct sk_buff *skb2;
	char *helper_name = NULL;
	char *helper_name = NULL;
	struct nf_conntrack_tuple tuple;
	struct nf_conntrack_tuple tuple;
	struct nfnl_cthelper *nlcth;
	bool tuple_set = false;
	bool tuple_set = false;


	if (nlh->nlmsg_flags & NLM_F_DUMP) {
	if (nlh->nlmsg_flags & NLM_F_DUMP) {
@@ -613,17 +613,12 @@ nfnl_cthelper_get(struct sock *nfnl, struct sk_buff *skb,
		tuple_set = true;
		tuple_set = true;
	}
	}


	for (i = 0; i < nf_ct_helper_hsize; i++) {
	list_for_each_entry(nlcth, &nfnl_cthelper_list, list) {
		hlist_for_each_entry_rcu(cur, &nf_ct_helper_hash[i], hnode) {
		cur = &nlcth->helper;

		if (helper_name &&
			/* skip non-userspace conntrack helpers. */
		    strncmp(cur->name, helper_name, NF_CT_HELPER_NAME_LEN))
			if (!(cur->flags & NF_CT_HELPER_F_USERSPACE))
			continue;
			continue;


			if (helper_name && strncmp(cur->name, helper_name,
						NF_CT_HELPER_NAME_LEN) != 0) {
				continue;
			}
		if (tuple_set &&
		if (tuple_set &&
		    (tuple.src.l3num != cur->tuple.src.l3num ||
		    (tuple.src.l3num != cur->tuple.src.l3num ||
		     tuple.dst.protonum != cur->tuple.dst.protonum))
		     tuple.dst.protonum != cur->tuple.dst.protonum))
@@ -652,7 +647,6 @@ nfnl_cthelper_get(struct sock *nfnl, struct sk_buff *skb,
		/* this avoids a loop in nfnetlink. */
		/* this avoids a loop in nfnetlink. */
		return ret == -EAGAIN ? -ENOBUFS : ret;
		return ret == -EAGAIN ? -ENOBUFS : ret;
	}
	}
	}
	return ret;
	return ret;
}
}


@@ -662,10 +656,10 @@ nfnl_cthelper_del(struct sock *nfnl, struct sk_buff *skb,
{
{
	char *helper_name = NULL;
	char *helper_name = NULL;
	struct nf_conntrack_helper *cur;
	struct nf_conntrack_helper *cur;
	struct hlist_node *tmp;
	struct nf_conntrack_tuple tuple;
	struct nf_conntrack_tuple tuple;
	bool tuple_set = false, found = false;
	bool tuple_set = false, found = false;
	int i, j = 0, ret;
	struct nfnl_cthelper *nlcth, *n;
	int j = 0, ret;


	if (tb[NFCTH_NAME])
	if (tb[NFCTH_NAME])
		helper_name = nla_data(tb[NFCTH_NAME]);
		helper_name = nla_data(tb[NFCTH_NAME]);
@@ -678,19 +672,14 @@ nfnl_cthelper_del(struct sock *nfnl, struct sk_buff *skb,
		tuple_set = true;
		tuple_set = true;
	}
	}


	for (i = 0; i < nf_ct_helper_hsize; i++) {
	list_for_each_entry_safe(nlcth, n, &nfnl_cthelper_list, list) {
		hlist_for_each_entry_safe(cur, tmp, &nf_ct_helper_hash[i],
		cur = &nlcth->helper;
								hnode) {
			/* skip non-userspace conntrack helpers. */
			if (!(cur->flags & NF_CT_HELPER_F_USERSPACE))
				continue;

		j++;
		j++;


			if (helper_name && strncmp(cur->name, helper_name,
		if (helper_name &&
						NF_CT_HELPER_NAME_LEN) != 0) {
		    strncmp(cur->name, helper_name, NF_CT_HELPER_NAME_LEN))
			continue;
			continue;
			}

		if (tuple_set &&
		if (tuple_set &&
		    (tuple.src.l3num != cur->tuple.src.l3num ||
		    (tuple.src.l3num != cur->tuple.src.l3num ||
		     tuple.dst.protonum != cur->tuple.dst.protonum))
		     tuple.dst.protonum != cur->tuple.dst.protonum))
@@ -699,9 +688,11 @@ nfnl_cthelper_del(struct sock *nfnl, struct sk_buff *skb,
		found = true;
		found = true;
		nf_conntrack_helper_unregister(cur);
		nf_conntrack_helper_unregister(cur);
		kfree(cur->expect_policy);
		kfree(cur->expect_policy);
			kfree(cur);

		}
		list_del(&nlcth->list);
		kfree(nlcth);
	}
	}

	/* Make sure we return success if we flush and there is no helpers */
	/* Make sure we return success if we flush and there is no helpers */
	return (found || j == 0) ? 0 : -ENOENT;
	return (found || j == 0) ? 0 : -ENOENT;
}
}
@@ -750,22 +741,16 @@ err_out:
static void __exit nfnl_cthelper_exit(void)
static void __exit nfnl_cthelper_exit(void)
{
{
	struct nf_conntrack_helper *cur;
	struct nf_conntrack_helper *cur;
	struct hlist_node *tmp;
	struct nfnl_cthelper *nlcth, *n;
	int i;


	nfnetlink_subsys_unregister(&nfnl_cthelper_subsys);
	nfnetlink_subsys_unregister(&nfnl_cthelper_subsys);


	for (i=0; i<nf_ct_helper_hsize; i++) {
	list_for_each_entry_safe(nlcth, n, &nfnl_cthelper_list, list) {
		hlist_for_each_entry_safe(cur, tmp, &nf_ct_helper_hash[i],
		cur = &nlcth->helper;
									hnode) {
			/* skip non-userspace conntrack helpers. */
			if (!(cur->flags & NF_CT_HELPER_F_USERSPACE))
				continue;


		nf_conntrack_helper_unregister(cur);
		nf_conntrack_helper_unregister(cur);
		kfree(cur->expect_policy);
		kfree(cur->expect_policy);
			kfree(cur);
		kfree(nlcth);
		}
	}
	}
}
}