diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index 249c30c47..2b5e9efe8 100755 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -3349,15 +3349,6 @@ static void nf_tables_rule_release(const struct nft_ctx *ctx, struct nft_rule *r nf_tables_rule_destroy(ctx, rule); } -/** nft_chain_validate - loop detection and hook validation - * - * @ctx: context containing call depth and base chain - * @chain: chain to validate - * - * Walk through the rules of the given chain and chase all jumps/gotos - * and set lookups until either the jump limit is hit or all reachable - * chains have been validated. - */ int nft_chain_validate(const struct nft_ctx *ctx, const struct nft_chain *chain) { struct nft_expr *expr, *last; @@ -3376,9 +3367,6 @@ int nft_chain_validate(const struct nft_ctx *ctx, const struct nft_chain *chain) if (!expr->ops->validate) continue; - /* This may call nft_chain_validate() recursively, - * callers that do so must increment ctx->level. - */ err = expr->ops->validate(ctx, expr, &data); if (err < 0) return err; @@ -9099,6 +9087,119 @@ int nft_chain_validate_hooks(const struct nft_chain *chain, } EXPORT_SYMBOL_GPL(nft_chain_validate_hooks); +/* + * 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 nft_check_loops(const struct nft_ctx *ctx, + const struct nft_set_ext *ext) +{ + const struct nft_data *data; + int ret; + + data = nft_set_ext_data(ext); + switch (data->verdict.code) { + case NFT_JUMP: + case NFT_GOTO: + ret = nf_tables_check_loops(ctx, data->verdict.chain); + break; + default: + ret = 0; + break; + } + + return ret; +} + +static int nf_tables_loop_check_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); + + if (nft_set_ext_exists(ext, NFT_SET_EXT_FLAGS) && + *nft_set_ext_flags(ext) & NFT_SET_ELEM_INTERVAL_END) + return 0; + + return nft_check_loops(ctx, ext); +} + +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; + struct nft_set *set; + struct nft_set_binding *binding; + struct nft_set_iter iter; + + if (ctx->chain == chain) + return -ELOOP; + + list_for_each_entry(rule, &chain->rules, list) { + nft_rule_for_each_expr(expr, last, rule) { + struct nft_immediate_expr *priv; + const struct nft_data *data; + int err; + + if (strcmp(expr->ops->type->name, "immediate")) + continue; + + priv = nft_expr_priv(expr); + if (priv->dreg != NFT_REG_VERDICT) + continue; + + data = &priv->data; + switch (data->verdict.code) { + case NFT_JUMP: + case NFT_GOTO: + err = nf_tables_check_loops(ctx, + data->verdict.chain); + if (err < 0) + return err; + break; + default: + break; + } + } + } + + list_for_each_entry(set, &ctx->table->sets, list) { + if (!nft_is_active_next(ctx->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(ctx->net); + 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_parse_u32_check - fetch u32 attribute and check for maximum value * @@ -9234,7 +9335,7 @@ static int nft_validate_register_store(const struct nft_ctx *ctx, if (data != NULL && (data->verdict.code == NFT_GOTO || data->verdict.code == NFT_JUMP)) { - err = nft_chain_validate(ctx, data->verdict.chain); + err = nf_tables_check_loops(ctx, data->verdict.chain); if (err < 0) return err; }