From patchwork Mon Dec 10 17:20:12 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Patrick McHardy X-Patchwork-Id: 204998 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 2C2472C0325 for ; Tue, 11 Dec 2012 04:31:47 +1100 (EST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751491Ab2LJRbp (ORCPT ); Mon, 10 Dec 2012 12:31:45 -0500 Received: from stinky.trash.net ([213.144.137.162]:36287 "EHLO stinky.trash.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751459Ab2LJRbm (ORCPT ); Mon, 10 Dec 2012 12:31:42 -0500 Received: from macbook.localnet (localhost [127.0.0.1]) by stinky.trash.net (Postfix) with ESMTP id 587949D2E6; Mon, 10 Dec 2012 18:20:29 +0100 (MET) From: kaber@trash.net To: pablo@netfilter.org Cc: netfilter-devel@vger.kernel.org, Patrick McHardy Subject: =?UTF-8?q?=5BPATCH=205/5=5D=20netfilter=3A=20nf=5Ftables=3A=20add=20loop=20detection?= Date: Mon, 10 Dec 2012 18:20:12 +0100 Message-Id: <1355160012-13952-6-git-send-email-kaber@trash.net> X-Mailer: git-send-email 1.7.11.7 In-Reply-To: <1355160012-13952-1-git-send-email-kaber@trash.net> References: <1355160012-13952-1-git-send-email-kaber@trash.net> MIME-Version: 1.0 Sender: netfilter-devel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netfilter-devel@vger.kernel.org From: Patrick McHardy Perform loop detection when adding new jump rules, new jump verdicts to a verdict map or when binding a verdict map to a new chain. The approach is pretty inefficient and probably can be improved by using some caching. For now just the simple approach is used to perform loop detection at all. Signed-off-by: Patrick McHardy --- include/net/netfilter/nf_tables.h | 1 + net/netfilter/nf_tables_api.c | 113 +++++++++++++++++++++++++++++++++++--- net/netfilter/nft_immediate.c | 11 ++++ 3 Dateien geändert, 118 Zeilen hinzugefügt(+), 7 Zeilen entfernt(-) diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h index 5e216de..99c500f 100644 --- a/include/net/netfilter/nf_tables.h +++ b/include/net/netfilter/nf_tables.h @@ -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; }; diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c index 22b14a5..2253593 100644 --- a/net/netfilter/nf_tables_api.c +++ b/net/netfilter/nf_tables_api.c @@ -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) diff --git a/net/netfilter/nft_immediate.c b/net/netfilter/nft_immediate.c index d1e901e..1bfeeaf 100644 --- a/net/netfilter/nft_immediate.c +++ b/net/netfilter/nft_immediate.c @@ -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 = {