|
@@ -27,14 +27,31 @@ struct nft_xt {
|
|
|
struct list_head head;
|
|
|
struct nft_expr_ops ops;
|
|
|
unsigned int refcnt;
|
|
|
+
|
|
|
+ /* Unlike other expressions, ops doesn't have static storage duration.
|
|
|
+ * nft core assumes they do. We use kfree_rcu so that nft core can
|
|
|
+ * can check expr->ops->size even after nft_compat->destroy() frees
|
|
|
+ * the nft_xt struct that holds the ops structure.
|
|
|
+ */
|
|
|
+ struct rcu_head rcu_head;
|
|
|
+};
|
|
|
+
|
|
|
+/* Used for matches where *info is larger than X byte */
|
|
|
+#define NFT_MATCH_LARGE_THRESH 192
|
|
|
+
|
|
|
+struct nft_xt_match_priv {
|
|
|
+ void *info;
|
|
|
};
|
|
|
|
|
|
-static void nft_xt_put(struct nft_xt *xt)
|
|
|
+static bool nft_xt_put(struct nft_xt *xt)
|
|
|
{
|
|
|
if (--xt->refcnt == 0) {
|
|
|
list_del(&xt->head);
|
|
|
- kfree(xt);
|
|
|
+ kfree_rcu(xt, rcu_head);
|
|
|
+ return true;
|
|
|
}
|
|
|
+
|
|
|
+ return false;
|
|
|
}
|
|
|
|
|
|
static int nft_compat_chain_validate_dependency(const char *tablename,
|
|
@@ -226,6 +243,7 @@ nft_target_init(const struct nft_ctx *ctx, const struct nft_expr *expr,
|
|
|
struct xt_target *target = expr->ops->data;
|
|
|
struct xt_tgchk_param par;
|
|
|
size_t size = XT_ALIGN(nla_len(tb[NFTA_TARGET_INFO]));
|
|
|
+ struct nft_xt *nft_xt;
|
|
|
u16 proto = 0;
|
|
|
bool inv = false;
|
|
|
union nft_entry e = {};
|
|
@@ -236,25 +254,22 @@ nft_target_init(const struct nft_ctx *ctx, const struct nft_expr *expr,
|
|
|
if (ctx->nla[NFTA_RULE_COMPAT]) {
|
|
|
ret = nft_parse_compat(ctx->nla[NFTA_RULE_COMPAT], &proto, &inv);
|
|
|
if (ret < 0)
|
|
|
- goto err;
|
|
|
+ return ret;
|
|
|
}
|
|
|
|
|
|
nft_target_set_tgchk_param(&par, ctx, target, info, &e, proto, inv);
|
|
|
|
|
|
ret = xt_check_target(&par, size, proto, inv);
|
|
|
if (ret < 0)
|
|
|
- goto err;
|
|
|
+ return ret;
|
|
|
|
|
|
/* The standard target cannot be used */
|
|
|
- if (target->target == NULL) {
|
|
|
- ret = -EINVAL;
|
|
|
- goto err;
|
|
|
- }
|
|
|
+ if (!target->target)
|
|
|
+ return -EINVAL;
|
|
|
|
|
|
+ nft_xt = container_of(expr->ops, struct nft_xt, ops);
|
|
|
+ nft_xt->refcnt++;
|
|
|
return 0;
|
|
|
-err:
|
|
|
- module_put(target->me);
|
|
|
- return ret;
|
|
|
}
|
|
|
|
|
|
static void
|
|
@@ -271,8 +286,8 @@ nft_target_destroy(const struct nft_ctx *ctx, const struct nft_expr *expr)
|
|
|
if (par.target->destroy != NULL)
|
|
|
par.target->destroy(&par);
|
|
|
|
|
|
- nft_xt_put(container_of(expr->ops, struct nft_xt, ops));
|
|
|
- module_put(target->me);
|
|
|
+ if (nft_xt_put(container_of(expr->ops, struct nft_xt, ops)))
|
|
|
+ module_put(target->me);
|
|
|
}
|
|
|
|
|
|
static int nft_target_dump(struct sk_buff *skb, const struct nft_expr *expr)
|
|
@@ -316,11 +331,11 @@ static int nft_target_validate(const struct nft_ctx *ctx,
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
-static void nft_match_eval(const struct nft_expr *expr,
|
|
|
- struct nft_regs *regs,
|
|
|
- const struct nft_pktinfo *pkt)
|
|
|
+static void __nft_match_eval(const struct nft_expr *expr,
|
|
|
+ struct nft_regs *regs,
|
|
|
+ const struct nft_pktinfo *pkt,
|
|
|
+ void *info)
|
|
|
{
|
|
|
- void *info = nft_expr_priv(expr);
|
|
|
struct xt_match *match = expr->ops->data;
|
|
|
struct sk_buff *skb = pkt->skb;
|
|
|
bool ret;
|
|
@@ -344,6 +359,22 @@ static void nft_match_eval(const struct nft_expr *expr,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+static void nft_match_large_eval(const struct nft_expr *expr,
|
|
|
+ struct nft_regs *regs,
|
|
|
+ const struct nft_pktinfo *pkt)
|
|
|
+{
|
|
|
+ struct nft_xt_match_priv *priv = nft_expr_priv(expr);
|
|
|
+
|
|
|
+ __nft_match_eval(expr, regs, pkt, priv->info);
|
|
|
+}
|
|
|
+
|
|
|
+static void nft_match_eval(const struct nft_expr *expr,
|
|
|
+ struct nft_regs *regs,
|
|
|
+ const struct nft_pktinfo *pkt)
|
|
|
+{
|
|
|
+ __nft_match_eval(expr, regs, pkt, nft_expr_priv(expr));
|
|
|
+}
|
|
|
+
|
|
|
static const struct nla_policy nft_match_policy[NFTA_MATCH_MAX + 1] = {
|
|
|
[NFTA_MATCH_NAME] = { .type = NLA_NUL_STRING },
|
|
|
[NFTA_MATCH_REV] = { .type = NLA_U32 },
|
|
@@ -404,13 +435,14 @@ static void match_compat_from_user(struct xt_match *m, void *in, void *out)
|
|
|
}
|
|
|
|
|
|
static int
|
|
|
-nft_match_init(const struct nft_ctx *ctx, const struct nft_expr *expr,
|
|
|
- const struct nlattr * const tb[])
|
|
|
+__nft_match_init(const struct nft_ctx *ctx, const struct nft_expr *expr,
|
|
|
+ const struct nlattr * const tb[],
|
|
|
+ void *info)
|
|
|
{
|
|
|
- void *info = nft_expr_priv(expr);
|
|
|
struct xt_match *match = expr->ops->data;
|
|
|
struct xt_mtchk_param par;
|
|
|
size_t size = XT_ALIGN(nla_len(tb[NFTA_MATCH_INFO]));
|
|
|
+ struct nft_xt *nft_xt;
|
|
|
u16 proto = 0;
|
|
|
bool inv = false;
|
|
|
union nft_entry e = {};
|
|
@@ -421,26 +453,50 @@ nft_match_init(const struct nft_ctx *ctx, const struct nft_expr *expr,
|
|
|
if (ctx->nla[NFTA_RULE_COMPAT]) {
|
|
|
ret = nft_parse_compat(ctx->nla[NFTA_RULE_COMPAT], &proto, &inv);
|
|
|
if (ret < 0)
|
|
|
- goto err;
|
|
|
+ return ret;
|
|
|
}
|
|
|
|
|
|
nft_match_set_mtchk_param(&par, ctx, match, info, &e, proto, inv);
|
|
|
|
|
|
ret = xt_check_match(&par, size, proto, inv);
|
|
|
if (ret < 0)
|
|
|
- goto err;
|
|
|
+ return ret;
|
|
|
|
|
|
+ nft_xt = container_of(expr->ops, struct nft_xt, ops);
|
|
|
+ nft_xt->refcnt++;
|
|
|
return 0;
|
|
|
-err:
|
|
|
- module_put(match->me);
|
|
|
+}
|
|
|
+
|
|
|
+static int
|
|
|
+nft_match_init(const struct nft_ctx *ctx, const struct nft_expr *expr,
|
|
|
+ const struct nlattr * const tb[])
|
|
|
+{
|
|
|
+ return __nft_match_init(ctx, expr, tb, nft_expr_priv(expr));
|
|
|
+}
|
|
|
+
|
|
|
+static int
|
|
|
+nft_match_large_init(const struct nft_ctx *ctx, const struct nft_expr *expr,
|
|
|
+ const struct nlattr * const tb[])
|
|
|
+{
|
|
|
+ struct nft_xt_match_priv *priv = nft_expr_priv(expr);
|
|
|
+ struct xt_match *m = expr->ops->data;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ priv->info = kmalloc(XT_ALIGN(m->matchsize), GFP_KERNEL);
|
|
|
+ if (!priv->info)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ ret = __nft_match_init(ctx, expr, tb, priv->info);
|
|
|
+ if (ret)
|
|
|
+ kfree(priv->info);
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
static void
|
|
|
-nft_match_destroy(const struct nft_ctx *ctx, const struct nft_expr *expr)
|
|
|
+__nft_match_destroy(const struct nft_ctx *ctx, const struct nft_expr *expr,
|
|
|
+ void *info)
|
|
|
{
|
|
|
struct xt_match *match = expr->ops->data;
|
|
|
- void *info = nft_expr_priv(expr);
|
|
|
struct xt_mtdtor_param par;
|
|
|
|
|
|
par.net = ctx->net;
|
|
@@ -450,13 +506,28 @@ nft_match_destroy(const struct nft_ctx *ctx, const struct nft_expr *expr)
|
|
|
if (par.match->destroy != NULL)
|
|
|
par.match->destroy(&par);
|
|
|
|
|
|
- nft_xt_put(container_of(expr->ops, struct nft_xt, ops));
|
|
|
- module_put(match->me);
|
|
|
+ if (nft_xt_put(container_of(expr->ops, struct nft_xt, ops)))
|
|
|
+ module_put(match->me);
|
|
|
}
|
|
|
|
|
|
-static int nft_match_dump(struct sk_buff *skb, const struct nft_expr *expr)
|
|
|
+static void
|
|
|
+nft_match_destroy(const struct nft_ctx *ctx, const struct nft_expr *expr)
|
|
|
+{
|
|
|
+ __nft_match_destroy(ctx, expr, nft_expr_priv(expr));
|
|
|
+}
|
|
|
+
|
|
|
+static void
|
|
|
+nft_match_large_destroy(const struct nft_ctx *ctx, const struct nft_expr *expr)
|
|
|
+{
|
|
|
+ struct nft_xt_match_priv *priv = nft_expr_priv(expr);
|
|
|
+
|
|
|
+ __nft_match_destroy(ctx, expr, priv->info);
|
|
|
+ kfree(priv->info);
|
|
|
+}
|
|
|
+
|
|
|
+static int __nft_match_dump(struct sk_buff *skb, const struct nft_expr *expr,
|
|
|
+ void *info)
|
|
|
{
|
|
|
- void *info = nft_expr_priv(expr);
|
|
|
struct xt_match *match = expr->ops->data;
|
|
|
|
|
|
if (nla_put_string(skb, NFTA_MATCH_NAME, match->name) ||
|
|
@@ -470,6 +541,18 @@ nla_put_failure:
|
|
|
return -1;
|
|
|
}
|
|
|
|
|
|
+static int nft_match_dump(struct sk_buff *skb, const struct nft_expr *expr)
|
|
|
+{
|
|
|
+ return __nft_match_dump(skb, expr, nft_expr_priv(expr));
|
|
|
+}
|
|
|
+
|
|
|
+static int nft_match_large_dump(struct sk_buff *skb, const struct nft_expr *e)
|
|
|
+{
|
|
|
+ struct nft_xt_match_priv *priv = nft_expr_priv(e);
|
|
|
+
|
|
|
+ return __nft_match_dump(skb, e, priv->info);
|
|
|
+}
|
|
|
+
|
|
|
static int nft_match_validate(const struct nft_ctx *ctx,
|
|
|
const struct nft_expr *expr,
|
|
|
const struct nft_data **data)
|
|
@@ -637,6 +720,7 @@ nft_match_select_ops(const struct nft_ctx *ctx,
|
|
|
{
|
|
|
struct nft_xt *nft_match;
|
|
|
struct xt_match *match;
|
|
|
+ unsigned int matchsize;
|
|
|
char *mt_name;
|
|
|
u32 rev, family;
|
|
|
int err;
|
|
@@ -654,13 +738,8 @@ nft_match_select_ops(const struct nft_ctx *ctx,
|
|
|
list_for_each_entry(nft_match, &nft_match_list, head) {
|
|
|
struct xt_match *match = nft_match->ops.data;
|
|
|
|
|
|
- if (nft_match_cmp(match, mt_name, rev, family)) {
|
|
|
- if (!try_module_get(match->me))
|
|
|
- return ERR_PTR(-ENOENT);
|
|
|
-
|
|
|
- nft_match->refcnt++;
|
|
|
+ if (nft_match_cmp(match, mt_name, rev, family))
|
|
|
return &nft_match->ops;
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
match = xt_request_find_match(family, mt_name, rev);
|
|
@@ -679,9 +758,8 @@ nft_match_select_ops(const struct nft_ctx *ctx,
|
|
|
goto err;
|
|
|
}
|
|
|
|
|
|
- nft_match->refcnt = 1;
|
|
|
+ nft_match->refcnt = 0;
|
|
|
nft_match->ops.type = &nft_match_type;
|
|
|
- nft_match->ops.size = NFT_EXPR_SIZE(XT_ALIGN(match->matchsize));
|
|
|
nft_match->ops.eval = nft_match_eval;
|
|
|
nft_match->ops.init = nft_match_init;
|
|
|
nft_match->ops.destroy = nft_match_destroy;
|
|
@@ -689,6 +767,18 @@ nft_match_select_ops(const struct nft_ctx *ctx,
|
|
|
nft_match->ops.validate = nft_match_validate;
|
|
|
nft_match->ops.data = match;
|
|
|
|
|
|
+ matchsize = NFT_EXPR_SIZE(XT_ALIGN(match->matchsize));
|
|
|
+ if (matchsize > NFT_MATCH_LARGE_THRESH) {
|
|
|
+ matchsize = NFT_EXPR_SIZE(sizeof(struct nft_xt_match_priv));
|
|
|
+
|
|
|
+ nft_match->ops.eval = nft_match_large_eval;
|
|
|
+ nft_match->ops.init = nft_match_large_init;
|
|
|
+ nft_match->ops.destroy = nft_match_large_destroy;
|
|
|
+ nft_match->ops.dump = nft_match_large_dump;
|
|
|
+ }
|
|
|
+
|
|
|
+ nft_match->ops.size = matchsize;
|
|
|
+
|
|
|
list_add(&nft_match->head, &nft_match_list);
|
|
|
|
|
|
return &nft_match->ops;
|
|
@@ -739,13 +829,8 @@ nft_target_select_ops(const struct nft_ctx *ctx,
|
|
|
list_for_each_entry(nft_target, &nft_target_list, head) {
|
|
|
struct xt_target *target = nft_target->ops.data;
|
|
|
|
|
|
- if (nft_target_cmp(target, tg_name, rev, family)) {
|
|
|
- if (!try_module_get(target->me))
|
|
|
- return ERR_PTR(-ENOENT);
|
|
|
-
|
|
|
- nft_target->refcnt++;
|
|
|
+ if (nft_target_cmp(target, tg_name, rev, family))
|
|
|
return &nft_target->ops;
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
target = xt_request_find_target(family, tg_name, rev);
|
|
@@ -764,7 +849,7 @@ nft_target_select_ops(const struct nft_ctx *ctx,
|
|
|
goto err;
|
|
|
}
|
|
|
|
|
|
- nft_target->refcnt = 1;
|
|
|
+ nft_target->refcnt = 0;
|
|
|
nft_target->ops.type = &nft_target_type;
|
|
|
nft_target->ops.size = NFT_EXPR_SIZE(XT_ALIGN(target->targetsize));
|
|
|
nft_target->ops.init = nft_target_init;
|
|
@@ -823,6 +908,32 @@ err_match:
|
|
|
|
|
|
static void __exit nft_compat_module_exit(void)
|
|
|
{
|
|
|
+ struct nft_xt *xt, *next;
|
|
|
+
|
|
|
+ /* list should be empty here, it can be non-empty only in case there
|
|
|
+ * was an error that caused nft_xt expr to not be initialized fully
|
|
|
+ * and noone else requested the same expression later.
|
|
|
+ *
|
|
|
+ * In this case, the lists contain 0-refcount entries that still
|
|
|
+ * hold module reference.
|
|
|
+ */
|
|
|
+ list_for_each_entry_safe(xt, next, &nft_target_list, head) {
|
|
|
+ struct xt_target *target = xt->ops.data;
|
|
|
+
|
|
|
+ if (WARN_ON_ONCE(xt->refcnt))
|
|
|
+ continue;
|
|
|
+ module_put(target->me);
|
|
|
+ kfree(xt);
|
|
|
+ }
|
|
|
+
|
|
|
+ list_for_each_entry_safe(xt, next, &nft_match_list, head) {
|
|
|
+ struct xt_match *match = xt->ops.data;
|
|
|
+
|
|
|
+ if (WARN_ON_ONCE(xt->refcnt))
|
|
|
+ continue;
|
|
|
+ module_put(match->me);
|
|
|
+ kfree(xt);
|
|
|
+ }
|
|
|
nfnetlink_subsys_unregister(&nfnl_compat_subsys);
|
|
|
nft_unregister_expr(&nft_target_type);
|
|
|
nft_unregister_expr(&nft_match_type);
|