[nf,3/5] netfilter: nf_tables: add type and hook validate routine

Message ID 20180515122343.29387-1-ap420073@gmail.com
State RFC
Delegated to: Pablo Neira
Headers show
Series
  • netfilter: nf_tables: add validate non-basechain ruleset routine
Related show

Commit Message

Taehee Yoo May 15, 2018, 12:23 p.m.
This patch adds validate callback to the nfnetlink_subsysem.
It validates type and hook of both basechain and non-basechain.
To validate type and hook, it constructs chain information array.
Like loop detection routine, validator travels each rules and sets
then marks type and hook value to the each chain information array.

example :

table ip test {
	chain prerouting {
		type nat hook prerouting priority 4;
		jump test1
	}
	chain postrouting {
		type nat hook postrouting priority 5;
		jump test1
	}
	chain input {
		type filter hook input priority 0;
		jump test1
	}
	chain outout {
		type filter hook output priority 0;
		jump test2
	}
	chain test1 {
		jump test2
		counter
	}
	chain test2 {
		counter
	}
}

The test1 has below chain information.
type = NFT_CHAIN_T_MIX
hook = (1 << NF_INET_PRE_ROUTING | 1 << NF_INET_POST_ROUTING |
	1 << NF_INET_LOCAL_IN)

And the test2 has below chain information.
type = NFT_CHAIN_T_MIX
hook = (1 << NF_INET_PRE_ROUTING | 1 << NF_INET_POST_ROUTING |
	1 << NF_INET_LOCAL_IN | 1 << NF_ONET_LOCAL_OUT)

The new type NFT_CHAIN_T_MIX means that chain has both filter and
nat type.
Then, validator calls expr->ops->validate()

Next patch makes expr->ops->validate() to use chain information array
insted of basechain's data.

Signed-off-by: Taehee Yoo <ap420073@gmail.com>
---
 include/linux/netfilter/nfnetlink.h |   1 +
 include/net/netfilter/nf_tables.h   |   1 +
 include/net/netns/nftables.h        |   3 +
 net/netfilter/nf_tables_api.c       | 262 ++++++++++++++++++++++++++++++++++++
 4 files changed, 267 insertions(+)

Patch

diff --git a/include/linux/netfilter/nfnetlink.h b/include/linux/netfilter/nfnetlink.h
index 34551f8..a641d52 100644
--- a/include/linux/netfilter/nfnetlink.h
+++ b/include/linux/netfilter/nfnetlink.h
@@ -29,6 +29,7 @@  struct nfnetlink_subsystem {
 	__u8 subsys_id;			/* nfnetlink subsystem ID */
 	__u8 cb_count;			/* number of callbacks */
 	const struct nfnl_callback *cb;	/* callback for individual types */
+	int (*validate)(struct net *net);
 	int (*commit)(struct net *net, struct sk_buff *skb);
 	int (*abort)(struct net *net, struct sk_buff *skb);
 	bool (*valid_genid)(struct net *net, u32 genid);
diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h
index 7eb4802..9959509 100644
--- a/include/net/netfilter/nf_tables.h
+++ b/include/net/netfilter/nf_tables.h
@@ -877,6 +877,7 @@  enum nft_chain_types {
 	NFT_CHAIN_T_DEFAULT = 0,
 	NFT_CHAIN_T_ROUTE,
 	NFT_CHAIN_T_NAT,
+	NFT_CHAIN_T_MIX,
 	NFT_CHAIN_T_MAX
 };
 
diff --git a/include/net/netns/nftables.h b/include/net/netns/nftables.h
index 29c3851..61e94e5 100644
--- a/include/net/netns/nftables.h
+++ b/include/net/netns/nftables.h
@@ -4,9 +4,12 @@ 
 
 #include <linux/list.h>
 
+struct nft_chain_info;
+
 struct netns_nftables {
 	struct list_head	tables;
 	struct list_head	commit_list;
+	struct nft_chain_info	*chain_info;
 	unsigned int		base_seq;
 	u8			gencursor;
 };
diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
index 13c2fc3..36d8fba 100644
--- a/net/netfilter/nf_tables_api.c
+++ b/net/netfilter/nf_tables_api.c
@@ -5841,6 +5841,267 @@  static void nf_tables_commit_release(struct net *net)
 	}
 }
 
+struct nft_chain_info {
+	u8		type;
+	unsigned int	hooknum;
+};
+
+static inline struct nft_chain_info *nft_get_chain_info(struct net *net,
+							struct nft_chain *chain)
+{
+	return net->nft.chain_info + chain->handle;
+}
+
+static int nft_validate_chain(struct net *net, struct nft_chain *chain)
+{
+	struct nft_table *table = chain->table;
+	struct nft_expr *expr, *last;
+	struct nft_rule *rule;
+	struct nft_ctx ctx;
+
+	list_for_each_entry(rule, &chain->rules, list) {
+		if (!nft_is_active_next(net, rule))
+			continue;
+		nft_rule_for_each_expr(expr, last, rule) {
+			const struct nft_data *data = NULL;
+			int err = 0;
+
+			if (!expr->ops->validate)
+				continue;
+
+			ctx.net		= net;
+			ctx.family	= table->family;
+			ctx.table	= table;
+			ctx.chain	= chain;
+			err = expr->ops->validate(&ctx, expr, &data);
+			if (err < 0)
+				return err;
+
+			if (!data)
+				continue;
+
+			switch (data->verdict.code) {
+			case NFT_JUMP:
+			case NFT_GOTO:
+				err = nft_validate_chain(net,
+							 data->verdict.chain);
+				if (err < 0)
+					return err;
+			default:
+				break;
+			}
+		}
+	}
+	return 0;
+}
+
+static int nft_mark_chain_info(struct net *net,
+			       struct nft_chain *pchain,
+			       struct nft_chain *cchain);
+
+static int nft_mark_chain_info_setelem(const struct nft_ctx *ctx,
+				       struct nft_set *set,
+				       const struct nft_set_iter *iter,
+				       struct nft_set_elem *elem)
+{
+	const struct nft_set_ext *ext = nft_set_elem_ext(set, elem->priv);
+	const struct nft_data *data;
+
+	if (nft_set_ext_exists(ext, NFT_SET_EXT_FLAGS) &&
+	    *nft_set_ext_flags(ext) & NFT_SET_ELEM_INTERVAL_END)
+		return 0;
+
+	data = nft_set_ext_data(ext);
+	switch (data->verdict.code) {
+	case NFT_JUMP:
+	case NFT_GOTO:
+		return nft_mark_chain_info(ctx->net, ctx->chain,
+					   data->verdict.chain);
+	default:
+		return 0;
+	}
+
+	return 0;
+}
+
+static int nft_mark_set_elem(struct net *net, struct nft_chain *chain)
+{
+	struct nft_ctx ctx;
+	struct nft_table *table = chain->table;
+	struct nft_set *set;
+	struct nft_set_binding *binding;
+	struct nft_set_iter iter;
+
+	list_for_each_entry(set, &table->sets, list) {
+		if (!nft_is_active_next(net, set))
+			continue;
+		if (!(set->flags & NFT_SET_MAP) ||
+		    set->dtype != NFT_DATA_VERDICT)
+			continue;
+
+		list_for_each_entry(binding, &set->bindings, list) {
+			if (!(binding->flags & NFT_SET_MAP) ||
+			    binding->chain != chain)
+				continue;
+
+			iter.genmask	= nft_genmask_next(net);
+			iter.skip	= 0;
+			iter.count	= 0;
+			iter.err	= 0;
+			iter.fn		= nft_mark_chain_info_setelem;
+
+			ctx.net		= net;
+			ctx.family	= table->family;
+			ctx.table	= table;
+			ctx.chain	= chain;
+			set->ops->walk(&ctx, set, &iter);
+			if (iter.err < 0)
+				return iter.err;
+		}
+	}
+	return 0;
+}
+
+static int nft_mark_rule(struct net *net, struct nft_chain *chain)
+{
+	struct nft_rule *rule;
+	struct nft_expr *expr, *last;
+
+	list_for_each_entry(rule, &chain->rules, list) {
+		if (!nft_is_active_next(net, rule))
+			continue;
+		nft_rule_for_each_expr(expr, last, rule) {
+			const struct nft_data *data = NULL;
+			int err;
+
+			if (!expr->ops->validate)
+				continue;
+			if (strcmp(expr->ops->type->name, "immediate"))
+				continue;
+
+			err = expr->ops->validate(NULL, expr, &data);
+			if (err < 0)
+				return err;
+
+			if (!data)
+				continue;
+
+			switch (data->verdict.code) {
+			case NFT_JUMP:
+			case NFT_GOTO:
+				err = nft_mark_chain_info(net, chain,
+							  data->verdict.chain);
+				if (err < 0)
+					return err;
+			default:
+				break;
+			}
+		}
+	}
+	return 0;
+}
+
+static int nft_mark_chain_info(struct net *net,
+			       struct nft_chain *pchain,
+			       struct nft_chain *cchain)
+{
+	struct nft_chain_info before;
+	struct nft_chain_info *pinfo = nft_get_chain_info(net, pchain);
+	struct nft_chain_info *cinfo = nft_get_chain_info(net, cchain);
+	int err = 0;
+
+	if (pchain != cchain) {
+		if (unlikely(nft_is_base_chain(cchain))) {
+			WARN_ON(1);
+			return -ELOOP;
+		}
+
+		before.type = cinfo->type;
+		before.hooknum = cinfo->hooknum;
+
+		if (cinfo->type && cinfo->type != pinfo->type)
+			cinfo->type = NFT_CHAIN_T_MIX;
+		else
+			cinfo->type = pinfo->type;
+		cinfo->hooknum |= pinfo->hooknum;
+
+		if (cinfo->type == before.type &&
+		    cinfo->hooknum == before.hooknum)
+			return 0;
+	}
+
+	err = nft_mark_rule(net, cchain);
+	if (err < 0)
+		return err;
+	return nft_mark_set_elem(net, cchain);
+}
+
+static int nf_tables_validate(struct net *net)
+{
+	struct nft_table *table;
+	struct nft_chain *chain;
+	struct nft_base_chain *basechain;
+	struct nft_chain_info *cinfo;
+	u64 hgenerator = 0;
+	int err = 0;
+
+	list_for_each_entry(table, &net->nft.tables, list) {
+		if (!nft_is_active_next(net, table))
+			continue;
+		hgenerator = max_t(u64, hgenerator, table->hgenerator);
+	}
+
+	if (!hgenerator)
+		return 0;
+
+	hgenerator++;
+	net->nft.chain_info = kvmalloc(hgenerator *
+				       sizeof(struct nft_chain_info),
+				       GFP_KERNEL);
+	if (!net->nft.chain_info)
+		return -ENOMEM;
+
+	list_for_each_entry(table, &net->nft.tables, list) {
+		if (!nft_is_active_next(net, table))
+			continue;
+
+		memset(net->nft.chain_info, 0,
+		       sizeof(struct nft_chain_info) * hgenerator);
+
+		list_for_each_entry(chain, &table->chains, list) {
+			if (!nft_is_active_next(net, chain))
+				continue;
+			if (!nft_is_base_chain(chain))
+				continue;
+
+			basechain = nft_base_chain(chain);
+			cinfo = nft_get_chain_info(net, chain);
+
+			cinfo->type = basechain->type->type;
+			cinfo->hooknum |= 1 << basechain->ops.hooknum;
+
+			err = nft_mark_chain_info(net, chain, chain);
+			if (err)
+				goto out;
+
+			err = nft_mark_set_elem(net, chain);
+			if (err < 0)
+				goto out;
+		}
+		list_for_each_entry(chain, &table->chains, list) {
+			if (!nft_is_active_next(net, chain))
+				continue;
+			err = nft_validate_chain(net, chain);
+			if (err < 0)
+				goto out;
+		}
+	}
+
+out:
+	kvfree(net->nft.chain_info);
+	return err;
+}
+
 static int nf_tables_commit(struct net *net, struct sk_buff *skb)
 {
 	struct nft_trans *trans, *next;
@@ -6128,6 +6389,7 @@  static const struct nfnetlink_subsystem nf_tables_subsys = {
 	.cb_count	= NFT_MSG_MAX,
 	.cb		= nf_tables_cb,
 	.commit		= nf_tables_commit,
+	.validate	= nf_tables_validate,
 	.abort		= nf_tables_abort,
 	.valid_genid	= nf_tables_valid_genid,
 };