]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
netfilter: x_tables: add .check_hooks to matches and targets
authorPablo Neira Ayuso <pablo@netfilter.org>
Tue, 28 Apr 2026 15:35:18 +0000 (17:35 +0200)
committerPablo Neira Ayuso <pablo@netfilter.org>
Thu, 30 Apr 2026 06:03:22 +0000 (08:03 +0200)
Add a new .check_hooks interface for checking if the match/target is
used from the validate hook according to its configuration.

Move existing conditional hook check based on the match/target
configuration from .checkentry to .check_hooks for the following
matches/targets:

- addrtype
- devgroup
- physdev
- policy
- set
- TCPMSS
- SET

This is a preparation patch to fix nft_compat, not functional changes
are intended.

Based on patch from Florian Westphal.

Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
include/linux/netfilter/x_tables.h
net/netfilter/x_tables.c
net/netfilter/xt_TCPMSS.c
net/netfilter/xt_addrtype.c
net/netfilter/xt_devgroup.c
net/netfilter/xt_physdev.c
net/netfilter/xt_policy.c
net/netfilter/xt_set.c

index 77c778d84d4cba54c6e7f11fc352ec37eb287ed1..a81b46af5118db0d508f8b3b473abb975db2589c 100644 (file)
@@ -146,6 +146,9 @@ struct xt_match {
        /* Called when user tries to insert an entry of this type. */
        int (*checkentry)(const struct xt_mtchk_param *);
 
+       /* Called to validate hooks based on the match configuration. */
+       int (*check_hooks)(const struct xt_mtchk_param *);
+
        /* Called when entry of this type deleted. */
        void (*destroy)(const struct xt_mtdtor_param *);
 #ifdef CONFIG_NETFILTER_XTABLES_COMPAT
@@ -187,6 +190,9 @@ struct xt_target {
        /* Should return 0 on success or an error code otherwise (-Exxxx). */
        int (*checkentry)(const struct xt_tgchk_param *);
 
+       /* Called to validate hooks based on the target configuration. */
+       int (*check_hooks)(const struct xt_tgchk_param *);
+
        /* Called when entry of this type deleted. */
        void (*destroy)(const struct xt_tgdtor_param *);
 #ifdef CONFIG_NETFILTER_XTABLES_COMPAT
@@ -279,8 +285,10 @@ bool xt_find_jump_offset(const unsigned int *offsets,
 
 int xt_check_proc_name(const char *name, unsigned int size);
 
+int xt_check_hooks_match(struct xt_mtchk_param *par);
 int xt_check_match(struct xt_mtchk_param *, unsigned int size, u16 proto,
                   bool inv_proto);
+int xt_check_hooks_target(struct xt_tgchk_param *par);
 int xt_check_target(struct xt_tgchk_param *, unsigned int size, u16 proto,
                    bool inv_proto);
 
index 9f837fb5ceb47d12bd13170ed084307d0357abcd..2c67c2e6b1328854558630f2074638917a25914c 100644 (file)
@@ -477,11 +477,9 @@ int xt_check_proc_name(const char *name, unsigned int size)
 }
 EXPORT_SYMBOL(xt_check_proc_name);
 
-int xt_check_match(struct xt_mtchk_param *par,
-                  unsigned int size, u16 proto, bool inv_proto)
+static int xt_check_match_common(struct xt_mtchk_param *par,
+                                unsigned int size, u16 proto, bool inv_proto)
 {
-       int ret;
-
        if (XT_ALIGN(par->match->matchsize) != size &&
            par->match->matchsize != -1) {
                /*
@@ -530,6 +528,14 @@ int xt_check_match(struct xt_mtchk_param *par,
                                    par->match->proto);
                return -EINVAL;
        }
+
+       return 0;
+}
+
+static int xt_checkentry_match(struct xt_mtchk_param *par)
+{
+       int ret;
+
        if (par->match->checkentry != NULL) {
                ret = par->match->checkentry(par);
                if (ret < 0)
@@ -538,8 +544,34 @@ int xt_check_match(struct xt_mtchk_param *par,
                        /* Flag up potential errors. */
                        return -EIO;
        }
+
+       return 0;
+}
+
+int xt_check_hooks_match(struct xt_mtchk_param *par)
+{
+       if (par->match->check_hooks != NULL)
+               return par->match->check_hooks(par);
+
        return 0;
 }
+EXPORT_SYMBOL_GPL(xt_check_hooks_match);
+
+int xt_check_match(struct xt_mtchk_param *par,
+                  unsigned int size, u16 proto, bool inv_proto)
+{
+       int ret;
+
+       ret = xt_check_match_common(par, size, proto, inv_proto);
+       if (ret < 0)
+               return ret;
+
+       ret = xt_check_hooks_match(par);
+       if (ret < 0)
+               return ret;
+
+       return xt_checkentry_match(par);
+}
 EXPORT_SYMBOL_GPL(xt_check_match);
 
 /** xt_check_entry_match - check that matches end before start of target
@@ -1012,11 +1044,9 @@ bool xt_find_jump_offset(const unsigned int *offsets,
 }
 EXPORT_SYMBOL(xt_find_jump_offset);
 
-int xt_check_target(struct xt_tgchk_param *par,
-                   unsigned int size, u16 proto, bool inv_proto)
+static int xt_check_target_common(struct xt_tgchk_param *par,
+                                 unsigned int size, u16 proto, bool inv_proto)
 {
-       int ret;
-
        if (XT_ALIGN(par->target->targetsize) != size) {
                pr_err_ratelimited("%s_tables: %s.%u target: invalid size %u (kernel) != (user) %u\n",
                                   xt_prefix[par->family], par->target->name,
@@ -1061,6 +1091,23 @@ int xt_check_target(struct xt_tgchk_param *par,
                                    par->target->proto);
                return -EINVAL;
        }
+
+       return 0;
+}
+
+int xt_check_hooks_target(struct xt_tgchk_param *par)
+{
+       if (par->target->check_hooks != NULL)
+               return par->target->check_hooks(par);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(xt_check_hooks_target);
+
+static int xt_checkentry_target(struct xt_tgchk_param *par)
+{
+       int ret;
+
        if (par->target->checkentry != NULL) {
                ret = par->target->checkentry(par);
                if (ret < 0)
@@ -1071,6 +1118,22 @@ int xt_check_target(struct xt_tgchk_param *par,
        }
        return 0;
 }
+
+int xt_check_target(struct xt_tgchk_param *par,
+                   unsigned int size, u16 proto, bool inv_proto)
+{
+       int ret;
+
+       ret = xt_check_target_common(par, size, proto, inv_proto);
+       if (ret < 0)
+               return ret;
+
+       ret = xt_check_hooks_target(par);
+       if (ret < 0)
+               return ret;
+
+       return xt_checkentry_target(par);
+}
 EXPORT_SYMBOL_GPL(xt_check_target);
 
 /**
index 116a885adb3cd6e93f82ee13f988e2dc0bee29f1..80e1634bc51f852ff149640d1f9462cf75770aa6 100644 (file)
@@ -247,6 +247,21 @@ tcpmss_tg6(struct sk_buff *skb, const struct xt_action_param *par)
 }
 #endif
 
+static int tcpmss_tg4_check_hooks(const struct xt_tgchk_param *par)
+{
+       const struct xt_tcpmss_info *info = par->targinfo;
+
+       if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
+           (par->hook_mask & ~((1 << NF_INET_FORWARD) |
+                          (1 << NF_INET_LOCAL_OUT) |
+                          (1 << NF_INET_POST_ROUTING))) != 0) {
+               pr_info_ratelimited("path-MTU clamping only supported in FORWARD, OUTPUT and POSTROUTING hooks\n");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
 /* Must specify -p tcp --syn */
 static inline bool find_syn_match(const struct xt_entry_match *m)
 {
@@ -262,17 +277,9 @@ static inline bool find_syn_match(const struct xt_entry_match *m)
 
 static int tcpmss_tg4_check(const struct xt_tgchk_param *par)
 {
-       const struct xt_tcpmss_info *info = par->targinfo;
        const struct ipt_entry *e = par->entryinfo;
        const struct xt_entry_match *ematch;
 
-       if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
-           (par->hook_mask & ~((1 << NF_INET_FORWARD) |
-                          (1 << NF_INET_LOCAL_OUT) |
-                          (1 << NF_INET_POST_ROUTING))) != 0) {
-               pr_info_ratelimited("path-MTU clamping only supported in FORWARD, OUTPUT and POSTROUTING hooks\n");
-               return -EINVAL;
-       }
        if (par->nft_compat)
                return 0;
 
@@ -286,17 +293,9 @@ static int tcpmss_tg4_check(const struct xt_tgchk_param *par)
 #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
 static int tcpmss_tg6_check(const struct xt_tgchk_param *par)
 {
-       const struct xt_tcpmss_info *info = par->targinfo;
        const struct ip6t_entry *e = par->entryinfo;
        const struct xt_entry_match *ematch;
 
-       if (info->mss == XT_TCPMSS_CLAMP_PMTU &&
-           (par->hook_mask & ~((1 << NF_INET_FORWARD) |
-                          (1 << NF_INET_LOCAL_OUT) |
-                          (1 << NF_INET_POST_ROUTING))) != 0) {
-               pr_info_ratelimited("path-MTU clamping only supported in FORWARD, OUTPUT and POSTROUTING hooks\n");
-               return -EINVAL;
-       }
        if (par->nft_compat)
                return 0;
 
@@ -312,6 +311,7 @@ static struct xt_target tcpmss_tg_reg[] __read_mostly = {
        {
                .family         = NFPROTO_IPV4,
                .name           = "TCPMSS",
+               .check_hooks    = tcpmss_tg4_check_hooks,
                .checkentry     = tcpmss_tg4_check,
                .target         = tcpmss_tg4,
                .targetsize     = sizeof(struct xt_tcpmss_info),
@@ -322,6 +322,7 @@ static struct xt_target tcpmss_tg_reg[] __read_mostly = {
        {
                .family         = NFPROTO_IPV6,
                .name           = "TCPMSS",
+               .check_hooks    = tcpmss_tg4_check_hooks,
                .checkentry     = tcpmss_tg6_check,
                .target         = tcpmss_tg6,
                .targetsize     = sizeof(struct xt_tcpmss_info),
index a7708894310716f154d6c66da5ce30a909d509eb..913dbe3aa5e29edb6f4507571fbf09b33dc6088c 100644 (file)
@@ -153,14 +153,10 @@ addrtype_mt_v1(const struct sk_buff *skb, struct xt_action_param *par)
        return ret;
 }
 
-static int addrtype_mt_checkentry_v1(const struct xt_mtchk_param *par)
+static int addrtype_mt_check_hooks(const struct xt_mtchk_param *par)
 {
-       const char *errmsg = "both incoming and outgoing interface limitation cannot be selected";
        struct xt_addrtype_info_v1 *info = par->matchinfo;
-
-       if (info->flags & XT_ADDRTYPE_LIMIT_IFACE_IN &&
-           info->flags & XT_ADDRTYPE_LIMIT_IFACE_OUT)
-               goto err;
+       const char *errmsg;
 
        if (par->hook_mask & ((1 << NF_INET_PRE_ROUTING) |
            (1 << NF_INET_LOCAL_IN)) &&
@@ -176,6 +172,21 @@ static int addrtype_mt_checkentry_v1(const struct xt_mtchk_param *par)
                goto err;
        }
 
+       return 0;
+err:
+       pr_info_ratelimited("%s\n", errmsg);
+       return -EINVAL;
+}
+
+static int addrtype_mt_checkentry_v1(const struct xt_mtchk_param *par)
+{
+       const char *errmsg = "both incoming and outgoing interface limitation cannot be selected";
+       struct xt_addrtype_info_v1 *info = par->matchinfo;
+
+       if (info->flags & XT_ADDRTYPE_LIMIT_IFACE_IN &&
+           info->flags & XT_ADDRTYPE_LIMIT_IFACE_OUT)
+               goto err;
+
 #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES)
        if (par->family == NFPROTO_IPV6) {
                if ((info->source | info->dest) & XT_ADDRTYPE_BLACKHOLE) {
@@ -211,6 +222,7 @@ static struct xt_match addrtype_mt_reg[] __read_mostly = {
                .family         = NFPROTO_IPV4,
                .revision       = 1,
                .match          = addrtype_mt_v1,
+               .check_hooks    = addrtype_mt_check_hooks,
                .checkentry     = addrtype_mt_checkentry_v1,
                .matchsize      = sizeof(struct xt_addrtype_info_v1),
                .me             = THIS_MODULE
@@ -221,6 +233,7 @@ static struct xt_match addrtype_mt_reg[] __read_mostly = {
                .family         = NFPROTO_IPV6,
                .revision       = 1,
                .match          = addrtype_mt_v1,
+               .check_hooks    = addrtype_mt_check_hooks,
                .checkentry     = addrtype_mt_checkentry_v1,
                .matchsize      = sizeof(struct xt_addrtype_info_v1),
                .me             = THIS_MODULE
index 9520dd00070b2222bfe0cc194526122b1ad66916..6d1a44ab5eeeb63adf5d1216612a2804eff6ae8c 100644 (file)
@@ -33,14 +33,10 @@ static bool devgroup_mt(const struct sk_buff *skb, struct xt_action_param *par)
        return true;
 }
 
-static int devgroup_mt_checkentry(const struct xt_mtchk_param *par)
+static int devgroup_mt_check_hooks(const struct xt_mtchk_param *par)
 {
        const struct xt_devgroup_info *info = par->matchinfo;
 
-       if (info->flags & ~(XT_DEVGROUP_MATCH_SRC | XT_DEVGROUP_INVERT_SRC |
-                           XT_DEVGROUP_MATCH_DST | XT_DEVGROUP_INVERT_DST))
-               return -EINVAL;
-
        if (info->flags & XT_DEVGROUP_MATCH_SRC &&
            par->hook_mask & ~((1 << NF_INET_PRE_ROUTING) |
                               (1 << NF_INET_LOCAL_IN) |
@@ -56,9 +52,21 @@ static int devgroup_mt_checkentry(const struct xt_mtchk_param *par)
        return 0;
 }
 
+static int devgroup_mt_checkentry(const struct xt_mtchk_param *par)
+{
+       const struct xt_devgroup_info *info = par->matchinfo;
+
+       if (info->flags & ~(XT_DEVGROUP_MATCH_SRC | XT_DEVGROUP_INVERT_SRC |
+                           XT_DEVGROUP_MATCH_DST | XT_DEVGROUP_INVERT_DST))
+               return -EINVAL;
+
+       return 0;
+}
+
 static struct xt_match devgroup_mt_reg __read_mostly = {
        .name           = "devgroup",
        .match          = devgroup_mt,
+       .check_hooks    = devgroup_mt_check_hooks,
        .checkentry     = devgroup_mt_checkentry,
        .matchsize      = sizeof(struct xt_devgroup_info),
        .family         = NFPROTO_UNSPEC,
index d2b0b52434fa90d7cbc13d085eae67caf66c29e2..dd98f758176c2ed13c3aae4f6b3095a96463f034 100644 (file)
@@ -91,14 +91,10 @@ match_outdev:
        return (!!ret ^ !(info->invert & XT_PHYSDEV_OP_OUT));
 }
 
-static int physdev_mt_check(const struct xt_mtchk_param *par)
+static int physdev_mt_check_hooks(const struct xt_mtchk_param *par)
 {
        const struct xt_physdev_info *info = par->matchinfo;
-       static bool brnf_probed __read_mostly;
 
-       if (!(info->bitmask & XT_PHYSDEV_OP_MASK) ||
-           info->bitmask & ~XT_PHYSDEV_OP_MASK)
-               return -EINVAL;
        if (info->bitmask & (XT_PHYSDEV_OP_OUT | XT_PHYSDEV_OP_ISOUT) &&
            (!(info->bitmask & XT_PHYSDEV_OP_BRIDGED) ||
             info->invert & XT_PHYSDEV_OP_BRIDGED) &&
@@ -107,6 +103,18 @@ static int physdev_mt_check(const struct xt_mtchk_param *par)
                return -EINVAL;
        }
 
+       return 0;
+}
+
+static int physdev_mt_check(const struct xt_mtchk_param *par)
+{
+       const struct xt_physdev_info *info = par->matchinfo;
+       static bool brnf_probed __read_mostly;
+
+       if (!(info->bitmask & XT_PHYSDEV_OP_MASK) ||
+           info->bitmask & ~XT_PHYSDEV_OP_MASK)
+               return -EINVAL;
+
 #define X(memb) strnlen(info->memb, sizeof(info->memb)) >= sizeof(info->memb)
        if (info->bitmask & XT_PHYSDEV_OP_IN) {
                if (info->physindev[0] == '\0')
@@ -141,6 +149,7 @@ static struct xt_match physdev_mt_reg[] __read_mostly = {
        {
                .name           = "physdev",
                .family         = NFPROTO_IPV4,
+               .check_hooks    = physdev_mt_check_hooks,
                .checkentry     = physdev_mt_check,
                .match          = physdev_mt,
                .matchsize      = sizeof(struct xt_physdev_info),
@@ -149,6 +158,7 @@ static struct xt_match physdev_mt_reg[] __read_mostly = {
        {
                .name           = "physdev",
                .family         = NFPROTO_IPV6,
+               .check_hooks    = physdev_mt_check_hooks,
                .checkentry     = physdev_mt_check,
                .match          = physdev_mt,
                .matchsize      = sizeof(struct xt_physdev_info),
index b5fa65558318f589dd394477933dc2c52131ac54..ff54e3a8581e1f85507081c0ca1ce606499ba7f7 100644 (file)
@@ -126,13 +126,10 @@ policy_mt(const struct sk_buff *skb, struct xt_action_param *par)
        return ret;
 }
 
-static int policy_mt_check(const struct xt_mtchk_param *par)
+static int policy_mt_check_hooks(const struct xt_mtchk_param *par)
 {
        const struct xt_policy_info *info = par->matchinfo;
-       const char *errmsg = "neither incoming nor outgoing policy selected";
-
-       if (!(info->flags & (XT_POLICY_MATCH_IN|XT_POLICY_MATCH_OUT)))
-               goto err;
+       const char *errmsg;
 
        if (par->hook_mask & ((1 << NF_INET_PRE_ROUTING) |
            (1 << NF_INET_LOCAL_IN)) && info->flags & XT_POLICY_MATCH_OUT) {
@@ -144,6 +141,21 @@ static int policy_mt_check(const struct xt_mtchk_param *par)
                errmsg = "input policy not valid in POSTROUTING and OUTPUT";
                goto err;
        }
+
+       return 0;
+err:
+       pr_info_ratelimited("%s\n", errmsg);
+       return -EINVAL;
+}
+
+static int policy_mt_check(const struct xt_mtchk_param *par)
+{
+       const struct xt_policy_info *info = par->matchinfo;
+       const char *errmsg = "neither incoming nor outgoing policy selected";
+
+       if (!(info->flags & (XT_POLICY_MATCH_IN|XT_POLICY_MATCH_OUT)))
+               goto err;
+
        if (info->len > XT_POLICY_MAX_ELEM) {
                errmsg = "too many policy elements";
                goto err;
@@ -158,6 +170,7 @@ static struct xt_match policy_mt_reg[] __read_mostly = {
        {
                .name           = "policy",
                .family         = NFPROTO_IPV4,
+               .check_hooks    = policy_mt_check_hooks,
                .checkentry     = policy_mt_check,
                .match          = policy_mt,
                .matchsize      = sizeof(struct xt_policy_info),
@@ -166,6 +179,7 @@ static struct xt_match policy_mt_reg[] __read_mostly = {
        {
                .name           = "policy",
                .family         = NFPROTO_IPV6,
+               .check_hooks    = policy_mt_check_hooks,
                .checkentry     = policy_mt_check,
                .match          = policy_mt,
                .matchsize      = sizeof(struct xt_policy_info),
index 731bc2cafae4bcd31ff47b559d90672cf0fc1170..4ae04bba93581c9df8760030646eb5689585eb9e 100644 (file)
@@ -430,6 +430,29 @@ set_target_v3(struct sk_buff *skb, const struct xt_action_param *par)
        return XT_CONTINUE;
 }
 
+static int
+set_target_v3_check_hooks(const struct xt_tgchk_param *par)
+{
+       const struct xt_set_info_target_v3 *info = par->targinfo;
+
+       if (info->map_set.index != IPSET_INVALID_ID) {
+               if (strncmp(par->table, "mangle", 7)) {
+                       pr_info_ratelimited("--map-set only usable from mangle table\n");
+                       return -EINVAL;
+               }
+               if (((info->flags & IPSET_FLAG_MAP_SKBPRIO) |
+                    (info->flags & IPSET_FLAG_MAP_SKBQUEUE)) &&
+                    (par->hook_mask & ~(1 << NF_INET_FORWARD |
+                                        1 << NF_INET_LOCAL_OUT |
+                                        1 << NF_INET_POST_ROUTING))) {
+                       pr_info_ratelimited("mapping of prio or/and queue is allowed only from OUTPUT/FORWARD/POSTROUTING chains\n");
+                       return -EINVAL;
+               }
+       }
+
+       return 0;
+}
+
 static int
 set_target_v3_checkentry(const struct xt_tgchk_param *par)
 {
@@ -459,20 +482,6 @@ set_target_v3_checkentry(const struct xt_tgchk_param *par)
        }
 
        if (info->map_set.index != IPSET_INVALID_ID) {
-               if (strncmp(par->table, "mangle", 7)) {
-                       pr_info_ratelimited("--map-set only usable from mangle table\n");
-                       ret = -EINVAL;
-                       goto cleanup_del;
-               }
-               if (((info->flags & IPSET_FLAG_MAP_SKBPRIO) |
-                    (info->flags & IPSET_FLAG_MAP_SKBQUEUE)) &&
-                    (par->hook_mask & ~(1 << NF_INET_FORWARD |
-                                        1 << NF_INET_LOCAL_OUT |
-                                        1 << NF_INET_POST_ROUTING))) {
-                       pr_info_ratelimited("mapping of prio or/and queue is allowed only from OUTPUT/FORWARD/POSTROUTING chains\n");
-                       ret = -EINVAL;
-                       goto cleanup_del;
-               }
                index = ip_set_nfnl_get_byindex(par->net,
                                                info->map_set.index);
                if (index == IPSET_INVALID_ID) {
@@ -672,6 +681,7 @@ static struct xt_target set_targets[] __read_mostly = {
                .family         = NFPROTO_IPV4,
                .target         = set_target_v3,
                .targetsize     = sizeof(struct xt_set_info_target_v3),
+               .check_hooks    = set_target_v3_check_hooks,
                .checkentry     = set_target_v3_checkentry,
                .destroy        = set_target_v3_destroy,
                .me             = THIS_MODULE
@@ -682,6 +692,7 @@ static struct xt_target set_targets[] __read_mostly = {
                .family         = NFPROTO_IPV6,
                .target         = set_target_v3,
                .targetsize     = sizeof(struct xt_set_info_target_v3),
+               .check_hooks    = set_target_v3_check_hooks,
                .checkentry     = set_target_v3_checkentry,
                .destroy        = set_target_v3_destroy,
                .me             = THIS_MODULE