linux/net/netfilter/nft_immediate.c
Pablo Neira Ayuso 0a771f7b26 netfilter: nf_tables: skip immediate deactivate in _PREPARE_ERROR
On error when building the rule, the immediate expression unbinds the
chain, hence objects can be deactivated by the transaction records.

Otherwise, it is possible to trigger the following warning:

 WARNING: CPU: 3 PID: 915 at net/netfilter/nf_tables_api.c:2013 nf_tables_chain_destroy+0x1f7/0x210 [nf_tables]
 CPU: 3 PID: 915 Comm: chain-bind-err- Not tainted 6.1.39 #1
 RIP: 0010:nf_tables_chain_destroy+0x1f7/0x210 [nf_tables]

Fixes: 4bedf9eee0 ("netfilter: nf_tables: fix chain binding transaction logic")
Reported-by: Kevin Rich <kevinrich1337@gmail.com>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
Signed-off-by: Florian Westphal <fw@strlen.de>
2023-07-26 16:48:49 +02:00

357 lines
8.2 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2008-2009 Patrick McHardy <kaber@trash.net>
*
* Development of this code funded by Astaro AG (http://www.astaro.com/)
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/netlink.h>
#include <linux/netfilter.h>
#include <linux/netfilter/nf_tables.h>
#include <net/netfilter/nf_tables_core.h>
#include <net/netfilter/nf_tables.h>
#include <net/netfilter/nf_tables_offload.h>
void nft_immediate_eval(const struct nft_expr *expr,
struct nft_regs *regs,
const struct nft_pktinfo *pkt)
{
const struct nft_immediate_expr *priv = nft_expr_priv(expr);
nft_data_copy(&regs->data[priv->dreg], &priv->data, priv->dlen);
}
static const struct nla_policy nft_immediate_policy[NFTA_IMMEDIATE_MAX + 1] = {
[NFTA_IMMEDIATE_DREG] = { .type = NLA_U32 },
[NFTA_IMMEDIATE_DATA] = { .type = NLA_NESTED },
};
static enum nft_data_types nft_reg_to_type(const struct nlattr *nla)
{
enum nft_data_types type;
u8 reg;
reg = ntohl(nla_get_be32(nla));
if (reg == NFT_REG_VERDICT)
type = NFT_DATA_VERDICT;
else
type = NFT_DATA_VALUE;
return type;
}
static int nft_immediate_init(const struct nft_ctx *ctx,
const struct nft_expr *expr,
const struct nlattr * const tb[])
{
struct nft_immediate_expr *priv = nft_expr_priv(expr);
struct nft_data_desc desc = {
.size = sizeof(priv->data),
};
int err;
if (tb[NFTA_IMMEDIATE_DREG] == NULL ||
tb[NFTA_IMMEDIATE_DATA] == NULL)
return -EINVAL;
desc.type = nft_reg_to_type(tb[NFTA_IMMEDIATE_DREG]);
err = nft_data_init(ctx, &priv->data, &desc, tb[NFTA_IMMEDIATE_DATA]);
if (err < 0)
return err;
priv->dlen = desc.len;
err = nft_parse_register_store(ctx, tb[NFTA_IMMEDIATE_DREG],
&priv->dreg, &priv->data, desc.type,
desc.len);
if (err < 0)
goto err1;
if (priv->dreg == NFT_REG_VERDICT) {
struct nft_chain *chain = priv->data.verdict.chain;
switch (priv->data.verdict.code) {
case NFT_JUMP:
case NFT_GOTO:
err = nf_tables_bind_chain(ctx, chain);
if (err < 0)
return err;
break;
default:
break;
}
}
return 0;
err1:
nft_data_release(&priv->data, desc.type);
return err;
}
static void nft_immediate_activate(const struct nft_ctx *ctx,
const struct nft_expr *expr)
{
const struct nft_immediate_expr *priv = nft_expr_priv(expr);
const struct nft_data *data = &priv->data;
struct nft_ctx chain_ctx;
struct nft_chain *chain;
struct nft_rule *rule;
if (priv->dreg == NFT_REG_VERDICT) {
switch (data->verdict.code) {
case NFT_JUMP:
case NFT_GOTO:
chain = data->verdict.chain;
if (!nft_chain_binding(chain))
break;
chain_ctx = *ctx;
chain_ctx.chain = chain;
list_for_each_entry(rule, &chain->rules, list)
nft_rule_expr_activate(&chain_ctx, rule);
nft_clear(ctx->net, chain);
break;
default:
break;
}
}
return nft_data_hold(&priv->data, nft_dreg_to_type(priv->dreg));
}
static void nft_immediate_chain_deactivate(const struct nft_ctx *ctx,
struct nft_chain *chain,
enum nft_trans_phase phase)
{
struct nft_ctx chain_ctx;
struct nft_rule *rule;
chain_ctx = *ctx;
chain_ctx.chain = chain;
list_for_each_entry(rule, &chain->rules, list)
nft_rule_expr_deactivate(&chain_ctx, rule, phase);
}
static void nft_immediate_deactivate(const struct nft_ctx *ctx,
const struct nft_expr *expr,
enum nft_trans_phase phase)
{
const struct nft_immediate_expr *priv = nft_expr_priv(expr);
const struct nft_data *data = &priv->data;
struct nft_chain *chain;
if (priv->dreg == NFT_REG_VERDICT) {
switch (data->verdict.code) {
case NFT_JUMP:
case NFT_GOTO:
chain = data->verdict.chain;
if (!nft_chain_binding(chain))
break;
switch (phase) {
case NFT_TRANS_PREPARE_ERROR:
nf_tables_unbind_chain(ctx, chain);
nft_deactivate_next(ctx->net, chain);
break;
case NFT_TRANS_PREPARE:
nft_immediate_chain_deactivate(ctx, chain, phase);
nft_deactivate_next(ctx->net, chain);
break;
default:
nft_immediate_chain_deactivate(ctx, chain, phase);
nft_chain_del(chain);
chain->bound = false;
nft_use_dec(&chain->table->use);
break;
}
break;
default:
break;
}
}
if (phase == NFT_TRANS_COMMIT)
return;
return nft_data_release(&priv->data, nft_dreg_to_type(priv->dreg));
}
static void nft_immediate_destroy(const struct nft_ctx *ctx,
const struct nft_expr *expr)
{
const struct nft_immediate_expr *priv = nft_expr_priv(expr);
const struct nft_data *data = &priv->data;
struct nft_rule *rule, *n;
struct nft_ctx chain_ctx;
struct nft_chain *chain;
if (priv->dreg != NFT_REG_VERDICT)
return;
switch (data->verdict.code) {
case NFT_JUMP:
case NFT_GOTO:
chain = data->verdict.chain;
if (!nft_chain_binding(chain))
break;
/* Rule construction failed, but chain is already bound:
* let the transaction records release this chain and its rules.
*/
if (chain->bound) {
nft_use_dec(&chain->use);
break;
}
/* Rule has been deleted, release chain and its rules. */
chain_ctx = *ctx;
chain_ctx.chain = chain;
nft_use_dec(&chain->use);
list_for_each_entry_safe(rule, n, &chain->rules, list) {
nft_use_dec(&chain->use);
list_del(&rule->list);
nf_tables_rule_destroy(&chain_ctx, rule);
}
nf_tables_chain_destroy(&chain_ctx);
break;
default:
break;
}
}
static int nft_immediate_dump(struct sk_buff *skb,
const struct nft_expr *expr, bool reset)
{
const struct nft_immediate_expr *priv = nft_expr_priv(expr);
if (nft_dump_register(skb, NFTA_IMMEDIATE_DREG, priv->dreg))
goto nla_put_failure;
return nft_data_dump(skb, NFTA_IMMEDIATE_DATA, &priv->data,
nft_dreg_to_type(priv->dreg), priv->dlen);
nla_put_failure:
return -1;
}
static int nft_immediate_validate(const struct nft_ctx *ctx,
const struct nft_expr *expr,
const struct nft_data **d)
{
const struct nft_immediate_expr *priv = nft_expr_priv(expr);
struct nft_ctx *pctx = (struct nft_ctx *)ctx;
const struct nft_data *data;
int err;
if (priv->dreg != NFT_REG_VERDICT)
return 0;
data = &priv->data;
switch (data->verdict.code) {
case NFT_JUMP:
case NFT_GOTO:
pctx->level++;
err = nft_chain_validate(ctx, data->verdict.chain);
if (err < 0)
return err;
pctx->level--;
break;
default:
break;
}
return 0;
}
static int nft_immediate_offload_verdict(struct nft_offload_ctx *ctx,
struct nft_flow_rule *flow,
const struct nft_immediate_expr *priv)
{
struct flow_action_entry *entry;
const struct nft_data *data;
entry = &flow->rule->action.entries[ctx->num_actions++];
data = &priv->data;
switch (data->verdict.code) {
case NF_ACCEPT:
entry->id = FLOW_ACTION_ACCEPT;
break;
case NF_DROP:
entry->id = FLOW_ACTION_DROP;
break;
default:
return -EOPNOTSUPP;
}
return 0;
}
static int nft_immediate_offload(struct nft_offload_ctx *ctx,
struct nft_flow_rule *flow,
const struct nft_expr *expr)
{
const struct nft_immediate_expr *priv = nft_expr_priv(expr);
if (priv->dreg == NFT_REG_VERDICT)
return nft_immediate_offload_verdict(ctx, flow, priv);
memcpy(&ctx->regs[priv->dreg].data, &priv->data, sizeof(priv->data));
return 0;
}
static bool nft_immediate_offload_action(const struct nft_expr *expr)
{
const struct nft_immediate_expr *priv = nft_expr_priv(expr);
if (priv->dreg == NFT_REG_VERDICT)
return true;
return false;
}
static bool nft_immediate_reduce(struct nft_regs_track *track,
const struct nft_expr *expr)
{
const struct nft_immediate_expr *priv = nft_expr_priv(expr);
if (priv->dreg != NFT_REG_VERDICT)
nft_reg_track_cancel(track, priv->dreg, priv->dlen);
return false;
}
static const struct nft_expr_ops nft_imm_ops = {
.type = &nft_imm_type,
.size = NFT_EXPR_SIZE(sizeof(struct nft_immediate_expr)),
.eval = nft_immediate_eval,
.init = nft_immediate_init,
.activate = nft_immediate_activate,
.deactivate = nft_immediate_deactivate,
.destroy = nft_immediate_destroy,
.dump = nft_immediate_dump,
.validate = nft_immediate_validate,
.reduce = nft_immediate_reduce,
.offload = nft_immediate_offload,
.offload_action = nft_immediate_offload_action,
};
struct nft_expr_type nft_imm_type __read_mostly = {
.name = "immediate",
.ops = &nft_imm_ops,
.policy = nft_immediate_policy,
.maxattr = NFTA_IMMEDIATE_MAX,
.owner = THIS_MODULE,
};