]> git.proxmox.com Git - mirror_ubuntu-bionic-kernel.git/blobdiff - net/netfilter/nft_compat.c
Input: elantech - enable middle button support on 2 ThinkPads
[mirror_ubuntu-bionic-kernel.git] / net / netfilter / nft_compat.c
index b89f4f65b2a0fbbcd725001e58ae373ab2c3156b..3bd637eadc4223ee81f3e76b8e4f22408647d27f 100644 (file)
@@ -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;
@@ -825,6 +910,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);