netfilter: nf_tables: disallow rule removal from chain binding
[platform/kernel/linux-starfive.git] / net / netfilter / nf_tables_api.c
index 3e841e4..895c6e4 100644 (file)
@@ -102,6 +102,7 @@ static const u8 nft2audit_op[NFT_MSG_MAX] = { // enum nf_tables_msg_types
        [NFT_MSG_NEWFLOWTABLE]  = AUDIT_NFT_OP_FLOWTABLE_REGISTER,
        [NFT_MSG_GETFLOWTABLE]  = AUDIT_NFT_OP_INVALID,
        [NFT_MSG_DELFLOWTABLE]  = AUDIT_NFT_OP_FLOWTABLE_UNREGISTER,
+       [NFT_MSG_GETSETELEM_RESET] = AUDIT_NFT_OP_SETELEM_RESET,
 };
 
 static void nft_validate_state_update(struct nft_table *table, u8 new_validate_state)
@@ -1373,7 +1374,7 @@ static int nf_tables_newtable(struct sk_buff *skb, const struct nfnl_info *info,
        if (table == NULL)
                goto err_kzalloc;
 
-       table->validate_state = NFT_VALIDATE_SKIP;
+       table->validate_state = nft_net->validate_state;
        table->name = nla_strdup(attr, GFP_KERNEL_ACCOUNT);
        if (table->name == NULL)
                goto err_strdup;
@@ -1431,7 +1432,7 @@ static int nft_flush_table(struct nft_ctx *ctx)
                if (!nft_is_active_next(ctx->net, chain))
                        continue;
 
-               if (nft_chain_is_bound(chain))
+               if (nft_chain_binding(chain))
                        continue;
 
                ctx->chain = chain;
@@ -1476,7 +1477,7 @@ static int nft_flush_table(struct nft_ctx *ctx)
                if (!nft_is_active_next(ctx->net, chain))
                        continue;
 
-               if (nft_chain_is_bound(chain))
+               if (nft_chain_binding(chain))
                        continue;
 
                ctx->chain = chain;
@@ -2909,6 +2910,9 @@ static int nf_tables_delchain(struct sk_buff *skb, const struct nfnl_info *info,
                return PTR_ERR(chain);
        }
 
+       if (nft_chain_binding(chain))
+               return -EOPNOTSUPP;
+
        nft_ctx_init(&ctx, net, skb, info->nlh, family, table, chain, nla);
 
        if (nla[NFTA_CHAIN_HOOK]) {
@@ -3421,6 +3425,18 @@ err:
        nfnetlink_set_err(ctx->net, ctx->portid, NFNLGRP_NFTABLES, -ENOBUFS);
 }
 
+static void audit_log_rule_reset(const struct nft_table *table,
+                                unsigned int base_seq,
+                                unsigned int nentries)
+{
+       char *buf = kasprintf(GFP_ATOMIC, "%s:%u",
+                             table->name, base_seq);
+
+       audit_log_nfcfg(buf, table->family, nentries,
+                       AUDIT_NFT_OP_RULE_RESET, GFP_ATOMIC);
+       kfree(buf);
+}
+
 struct nft_rule_dump_ctx {
        char *table;
        char *chain;
@@ -3467,6 +3483,10 @@ cont:
 cont_skip:
                (*idx)++;
        }
+
+       if (reset && *idx)
+               audit_log_rule_reset(table, cb->seq, *idx);
+
        return 0;
 }
 
@@ -3634,6 +3654,9 @@ static int nf_tables_getrule(struct sk_buff *skb, const struct nfnl_info *info,
        if (err < 0)
                goto err_fill_rule_info;
 
+       if (reset)
+               audit_log_rule_reset(table, nft_pernet(net)->base_seq, 1);
+
        return nfnetlink_unicast(skb2, net, NETLINK_CB(skb).portid);
 
 err_fill_rule_info:
@@ -3675,6 +3698,9 @@ int nft_chain_validate(const struct nft_ctx *ctx, const struct nft_chain *chain)
                return -EMLINK;
 
        list_for_each_entry(rule, &chain->rules, list) {
+               if (fatal_signal_pending(current))
+                       return -EINTR;
+
                if (!nft_is_active_next(ctx->net, rule))
                        continue;
 
@@ -3948,6 +3974,11 @@ static int nf_tables_newrule(struct sk_buff *skb, const struct nfnl_info *info,
        }
 
        if (info->nlh->nlmsg_flags & NLM_F_REPLACE) {
+               if (nft_chain_binding(chain)) {
+                       err = -EOPNOTSUPP;
+                       goto err_destroy_flow_rule;
+               }
+
                err = nft_delrule(&ctx, old_rule);
                if (err < 0)
                        goto err_destroy_flow_rule;
@@ -4055,7 +4086,7 @@ static int nf_tables_delrule(struct sk_buff *skb, const struct nfnl_info *info,
                        NL_SET_BAD_ATTR(extack, nla[NFTA_RULE_CHAIN]);
                        return PTR_ERR(chain);
                }
-               if (nft_chain_is_bound(chain))
+               if (nft_chain_binding(chain))
                        return -EOPNOTSUPP;
        }
 
@@ -4089,7 +4120,7 @@ static int nf_tables_delrule(struct sk_buff *skb, const struct nfnl_info *info,
                list_for_each_entry(chain, &table->chains, list) {
                        if (!nft_is_active_next(net, chain))
                                continue;
-                       if (nft_chain_is_bound(chain))
+                       if (nft_chain_binding(chain))
                                continue;
 
                        ctx.chain = chain;
@@ -5621,13 +5652,25 @@ static int nf_tables_dump_setelem(const struct nft_ctx *ctx,
        return nf_tables_fill_setelem(args->skb, set, elem, args->reset);
 }
 
+static void audit_log_nft_set_reset(const struct nft_table *table,
+                                   unsigned int base_seq,
+                                   unsigned int nentries)
+{
+       char *buf = kasprintf(GFP_ATOMIC, "%s:%u", table->name, base_seq);
+
+       audit_log_nfcfg(buf, table->family, nentries,
+                       AUDIT_NFT_OP_SETELEM_RESET, GFP_ATOMIC);
+       kfree(buf);
+}
+
 struct nft_set_dump_ctx {
        const struct nft_set    *set;
        struct nft_ctx          ctx;
 };
 
 static int nft_set_catchall_dump(struct net *net, struct sk_buff *skb,
-                                const struct nft_set *set, bool reset)
+                                const struct nft_set *set, bool reset,
+                                unsigned int base_seq)
 {
        struct nft_set_elem_catchall *catchall;
        u8 genmask = nft_genmask_cur(net);
@@ -5643,6 +5686,8 @@ static int nft_set_catchall_dump(struct net *net, struct sk_buff *skb,
 
                elem.priv = catchall->elem;
                ret = nf_tables_fill_setelem(skb, set, &elem, reset);
+               if (reset && !ret)
+                       audit_log_nft_set_reset(set->table, base_seq, 1);
                break;
        }
 
@@ -5722,12 +5767,17 @@ static int nf_tables_dump_set(struct sk_buff *skb, struct netlink_callback *cb)
        set->ops->walk(&dump_ctx->ctx, set, &args.iter);
 
        if (!args.iter.err && args.iter.count == cb->args[0])
-               args.iter.err = nft_set_catchall_dump(net, skb, set, reset);
-       rcu_read_unlock();
-
+               args.iter.err = nft_set_catchall_dump(net, skb, set,
+                                                     reset, cb->seq);
        nla_nest_end(skb, nest);
        nlmsg_end(skb, nlh);
 
+       if (reset && args.iter.count > args.iter.skip)
+               audit_log_nft_set_reset(table, cb->seq,
+                                       args.iter.count - args.iter.skip);
+
+       rcu_read_unlock();
+
        if (args.iter.err && args.iter.err != -EMSGSIZE)
                return args.iter.err;
        if (args.iter.count == cb->args[0])
@@ -5952,13 +6002,13 @@ static int nf_tables_getsetelem(struct sk_buff *skb,
        struct netlink_ext_ack *extack = info->extack;
        u8 genmask = nft_genmask_cur(info->net);
        u8 family = info->nfmsg->nfgen_family;
+       int rem, err = 0, nelems = 0;
        struct net *net = info->net;
        struct nft_table *table;
        struct nft_set *set;
        struct nlattr *attr;
        struct nft_ctx ctx;
        bool reset = false;
-       int rem, err = 0;
 
        table = nft_table_lookup(net, nla[NFTA_SET_ELEM_LIST_TABLE], family,
                                 genmask, 0);
@@ -6001,8 +6051,13 @@ static int nf_tables_getsetelem(struct sk_buff *skb,
                        NL_SET_BAD_ATTR(extack, attr);
                        break;
                }
+               nelems++;
        }
 
+       if (reset)
+               audit_log_nft_set_reset(table, nft_pernet(net)->base_seq,
+                                       nelems);
+
        return err;
 }
 
@@ -9051,9 +9106,8 @@ static int nf_tables_validate(struct net *net)
                                return -EAGAIN;
 
                        nft_validate_state_update(table, NFT_VALIDATE_SKIP);
+                       break;
                }
-
-               break;
        }
 
        return 0;
@@ -9457,9 +9511,9 @@ static void nft_trans_gc_work(struct work_struct *work)
        struct nft_trans_gc *trans, *next;
        LIST_HEAD(trans_gc_list);
 
-       spin_lock(&nf_tables_destroy_list_lock);
+       spin_lock(&nf_tables_gc_list_lock);
        list_splice_init(&nf_tables_gc_list, &trans_gc_list);
-       spin_unlock(&nf_tables_destroy_list_lock);
+       spin_unlock(&nf_tables_gc_list_lock);
 
        list_for_each_entry_safe(trans, next, &trans_gc_list, list) {
                list_del(&trans->list);
@@ -9799,8 +9853,10 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb)
        }
 
        /* 0. Validate ruleset, otherwise roll back for error reporting. */
-       if (nf_tables_validate(net) < 0)
+       if (nf_tables_validate(net) < 0) {
+               nft_net->validate_state = NFT_VALIDATE_DO;
                return -EAGAIN;
+       }
 
        err = nft_flow_rule_offload_commit(net);
        if (err < 0)
@@ -10059,6 +10115,7 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb)
        nf_tables_commit_audit_log(&adl, nft_net->base_seq);
 
        nft_gc_seq_end(nft_net, gc_seq);
+       nft_net->validate_state = NFT_VALIDATE_SKIP;
        nf_tables_commit_release(net);
 
        return 0;
@@ -10335,8 +10392,12 @@ static int nf_tables_abort(struct net *net, struct sk_buff *skb,
                           enum nfnl_abort_action action)
 {
        struct nftables_pernet *nft_net = nft_pernet(net);
-       int ret = __nf_tables_abort(net, action);
+       unsigned int gc_seq;
+       int ret;
 
+       gc_seq = nft_gc_seq_begin(nft_net);
+       ret = __nf_tables_abort(net, action);
+       nft_gc_seq_end(nft_net, gc_seq);
        mutex_unlock(&nft_net->commit_mutex);
 
        return ret;
@@ -10479,6 +10540,9 @@ static int nf_tables_check_loops(const struct nft_ctx *ctx,
        if (ctx->chain == chain)
                return -ELOOP;
 
+       if (fatal_signal_pending(current))
+               return -EINTR;
+
        list_for_each_entry(rule, &chain->rules, list) {
                nft_rule_for_each_expr(expr, last, rule) {
                        struct nft_immediate_expr *priv;
@@ -10998,7 +11062,7 @@ static void __nft_release_table(struct net *net, struct nft_table *table)
        ctx.family = table->family;
        ctx.table = table;
        list_for_each_entry(chain, &table->chains, list) {
-               if (nft_chain_is_bound(chain))
+               if (nft_chain_binding(chain))
                        continue;
 
                ctx.chain = chain;
@@ -11071,7 +11135,7 @@ static int nft_rcv_nl_event(struct notifier_block *this, unsigned long event,
        gc_seq = nft_gc_seq_begin(nft_net);
 
        if (!list_empty(&nf_tables_destroy_list))
-               rcu_barrier();
+               nf_tables_trans_destroy_flush_work();
 again:
        list_for_each_entry(table, &nft_net->tables, list) {
                if (nft_table_has_owner(table) &&
@@ -11115,6 +11179,7 @@ static int __net_init nf_tables_init_net(struct net *net)
        mutex_init(&nft_net->commit_mutex);
        nft_net->base_seq = 1;
        nft_net->gc_seq = 0;
+       nft_net->validate_state = NFT_VALIDATE_SKIP;
 
        return 0;
 }