@@ -270,6 +270,7 @@ struct nft_expr_ops {
void (*destroy)(const struct nft_expr *expr);
int (*dump)(struct sk_buff *skb,
const struct nft_expr *expr);
+ const struct nft_data * (*get_verdict)(const struct nft_expr *expr);
const struct nft_expr_type *type;
};
@@ -1972,12 +1972,21 @@ static int nf_tables_bind_check_setelem(const struct nft_ctx *ctx,
int nf_tables_bind_set(const struct nft_ctx *ctx, struct nft_set *set,
struct nft_set_binding *binding)
{
+ struct nft_set_binding *i;
struct nft_set_iter iter;
if (!list_empty(&set->bindings) && set->flags & NFT_SET_ANONYMOUS)
return -EBUSY;
if (set->flags & NFT_SET_MAP) {
+ /* If the set is already bound to the same chain all
+ * jumps are already validated for that chain.
+ */
+ list_for_each_entry(i, &set->bindings, list) {
+ if (i->chain == binding->chain)
+ goto bind;
+ }
+
iter.skip = 0;
iter.count = 0;
iter.err = 0;
@@ -1992,7 +2001,7 @@ int nf_tables_bind_set(const struct nft_ctx *ctx, struct nft_set *set,
return iter.err;
}
}
-
+bind:
binding->chain = ctx->chain;
list_add_tail(&binding->list, &set->bindings);
return 0;
@@ -2457,6 +2466,90 @@ static const struct nfnetlink_subsystem nf_tables_subsys = {
.cb = nf_tables_cb,
};
+/*
+ * Loop detection - walk through the ruleset beginning at the destination chain
+ * of a new jump until either the source chain is reached (loop) or all
+ * reachable chains have been traversed.
+ *
+ * The loop check is performed whenever a new jump verdict is added to an
+ * expression or verdict map or a verdict map is bound to a new chain.
+ */
+
+static int nf_tables_check_loops(const struct nft_ctx *ctx,
+ const struct nft_chain *chain);
+
+static int nf_tables_loop_check_setelem(const struct nft_ctx *ctx,
+ const struct nft_set *set,
+ const struct nft_set_iter *iter,
+ const struct nft_set_elem *elem)
+{
+ switch (elem->data.verdict) {
+ case NFT_JUMP:
+ case NFT_GOTO:
+ return nf_tables_check_loops(ctx, elem->data.chain);
+ default:
+ return 0;
+ }
+}
+
+static int nf_tables_check_loops(const struct nft_ctx *ctx,
+ const struct nft_chain *chain)
+{
+ const struct nft_rule *rule;
+ const struct nft_expr *expr, *last;
+ const struct nft_data *data;
+ const struct nft_set *set;
+ struct nft_set_binding *binding;
+ struct nft_set_iter iter;
+ int err;
+
+ if (ctx->chain == chain)
+ return -ELOOP;
+
+ list_for_each_entry(rule, &chain->rules, list) {
+ nft_rule_for_each_expr(expr, last, rule) {
+ if (!expr->ops->get_verdict)
+ continue;
+
+ data = expr->ops->get_verdict(expr);
+ if (data == NULL)
+ break;
+
+ switch (data->verdict) {
+ case NFT_JUMP:
+ case NFT_GOTO:
+ err = nf_tables_check_loops(ctx, data->chain);
+ if (err < 0)
+ return err;
+ default:
+ break;
+ }
+ }
+ }
+
+ list_for_each_entry(set, &ctx->table->sets, list) {
+ if (!(set->flags & NFT_SET_MAP) ||
+ set->dtype != NFT_DATA_VERDICT)
+ continue;
+
+ list_for_each_entry(binding, &set->bindings, list) {
+ if (binding->chain != chain)
+ continue;
+
+ iter.skip = 0;
+ iter.count = 0;
+ iter.err = 0;
+ iter.fn = nf_tables_loop_check_setelem;
+
+ set->ops->walk(ctx, set, &iter);
+ if (iter.err < 0)
+ return iter.err;
+ }
+ }
+
+ return 0;
+}
+
/**
* nft_validate_input_register - validate an expressions' input register
*
@@ -2510,19 +2603,25 @@ int nft_validate_data_load(const struct nft_ctx *ctx, enum nft_registers reg,
const struct nft_data *data,
enum nft_data_types type)
{
+ int err;
+
switch (reg) {
case NFT_REG_VERDICT:
if (data == NULL || type != NFT_DATA_VERDICT)
return -EINVAL;
- if ((data->verdict == NFT_GOTO || data->verdict == NFT_JUMP) &&
- ctx->chain->level + 1 > data->chain->level) {
- if (ctx->chain->level + 1 == NFT_JUMP_STACK_SIZE)
- return -EMLINK;
- data->chain->level = ctx->chain->level + 1;
+ if (data->verdict == NFT_GOTO || data->verdict == NFT_JUMP) {
+ err = nf_tables_check_loops(ctx, data->chain);
+ if (err < 0)
+ return err;
+
+ if (ctx->chain->level + 1 > data->chain->level) {
+ if (ctx->chain->level + 1 == NFT_JUMP_STACK_SIZE)
+ return -EMLINK;
+ data->chain->level = ctx->chain->level + 1;
+ }
}
- // FIXME: do loop detection
return 0;
default:
if (data != NULL && type != NFT_DATA_VALUE)
@@ -90,6 +90,16 @@ nla_put_failure:
return -1;
}
+static const struct nft_data *nft_immediate_get_verdict(const struct nft_expr *expr)
+{
+ const struct nft_immediate_expr *priv = nft_expr_priv(expr);
+
+ if (priv->dreg == NFT_REG_VERDICT)
+ return &priv->data;
+ else
+ return NULL;
+}
+
static struct nft_expr_type nft_imm_type;
static const struct nft_expr_ops nft_imm_ops = {
.type = &nft_imm_type,
@@ -98,6 +108,7 @@ static const struct nft_expr_ops nft_imm_ops = {
.init = nft_immediate_init,
.destroy = nft_immediate_destroy,
.dump = nft_immediate_dump,
+ .get_verdict = nft_immediate_get_verdict,
};
static struct nft_expr_type nft_imm_type __read_mostly = {