netfilter: nf_tables: honor set timeout and garbage collection updates
authorPablo Neira Ayuso <pablo@netfilter.org>
Mon, 19 Dec 2022 19:10:12 +0000 (20:10 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 12 Jan 2023 10:59:09 +0000 (11:59 +0100)
[ Upstream commit 123b99619cca94bdca0bf7bde9abe28f0a0dfe06 ]

Set timeout and garbage collection interval updates are ignored on
updates. Add transaction to update global set element timeout and
garbage collection interval.

Fixes: 96518518cc41 ("netfilter: add nftables")
Suggested-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
include/net/netfilter/nf_tables.h
net/netfilter/nf_tables_api.c

index 5377dbf..80df8ff 100644 (file)
@@ -562,7 +562,9 @@ void *nft_set_catchall_gc(const struct nft_set *set);
 
 static inline unsigned long nft_set_gc_interval(const struct nft_set *set)
 {
-       return set->gc_int ? msecs_to_jiffies(set->gc_int) : HZ;
+       u32 gc_int = READ_ONCE(set->gc_int);
+
+       return gc_int ? msecs_to_jiffies(gc_int) : HZ;
 }
 
 /**
@@ -1511,6 +1513,9 @@ struct nft_trans_rule {
 struct nft_trans_set {
        struct nft_set                  *set;
        u32                             set_id;
+       u32                             gc_int;
+       u64                             timeout;
+       bool                            update;
        bool                            bound;
 };
 
@@ -1520,6 +1525,12 @@ struct nft_trans_set {
        (((struct nft_trans_set *)trans->data)->set_id)
 #define nft_trans_set_bound(trans)     \
        (((struct nft_trans_set *)trans->data)->bound)
+#define nft_trans_set_update(trans)    \
+       (((struct nft_trans_set *)trans->data)->update)
+#define nft_trans_set_timeout(trans)   \
+       (((struct nft_trans_set *)trans->data)->timeout)
+#define nft_trans_set_gc_int(trans)    \
+       (((struct nft_trans_set *)trans->data)->gc_int)
 
 struct nft_trans_chain {
        bool                            update;
index 82fe54b..81bd13b 100644 (file)
@@ -465,8 +465,9 @@ static int nft_delrule_by_chain(struct nft_ctx *ctx)
        return 0;
 }
 
-static int nft_trans_set_add(const struct nft_ctx *ctx, int msg_type,
-                            struct nft_set *set)
+static int __nft_trans_set_add(const struct nft_ctx *ctx, int msg_type,
+                              struct nft_set *set,
+                              const struct nft_set_desc *desc)
 {
        struct nft_trans *trans;
 
@@ -474,17 +475,28 @@ static int nft_trans_set_add(const struct nft_ctx *ctx, int msg_type,
        if (trans == NULL)
                return -ENOMEM;
 
-       if (msg_type == NFT_MSG_NEWSET && ctx->nla[NFTA_SET_ID] != NULL) {
+       if (msg_type == NFT_MSG_NEWSET && ctx->nla[NFTA_SET_ID] && !desc) {
                nft_trans_set_id(trans) =
                        ntohl(nla_get_be32(ctx->nla[NFTA_SET_ID]));
                nft_activate_next(ctx->net, set);
        }
        nft_trans_set(trans) = set;
+       if (desc) {
+               nft_trans_set_update(trans) = true;
+               nft_trans_set_gc_int(trans) = desc->gc_int;
+               nft_trans_set_timeout(trans) = desc->timeout;
+       }
        nft_trans_commit_list_add_tail(ctx->net, trans);
 
        return 0;
 }
 
+static int nft_trans_set_add(const struct nft_ctx *ctx, int msg_type,
+                            struct nft_set *set)
+{
+       return __nft_trans_set_add(ctx, msg_type, set, NULL);
+}
+
 static int nft_delset(const struct nft_ctx *ctx, struct nft_set *set)
 {
        int err;
@@ -3899,8 +3911,10 @@ static int nf_tables_fill_set_concat(struct sk_buff *skb,
 static int nf_tables_fill_set(struct sk_buff *skb, const struct nft_ctx *ctx,
                              const struct nft_set *set, u16 event, u16 flags)
 {
-       struct nlmsghdr *nlh;
+       u64 timeout = READ_ONCE(set->timeout);
+       u32 gc_int = READ_ONCE(set->gc_int);
        u32 portid = ctx->portid;
+       struct nlmsghdr *nlh;
        struct nlattr *nest;
        u32 seq = ctx->seq;
        int i;
@@ -3936,13 +3950,13 @@ static int nf_tables_fill_set(struct sk_buff *skb, const struct nft_ctx *ctx,
            nla_put_be32(skb, NFTA_SET_OBJ_TYPE, htonl(set->objtype)))
                goto nla_put_failure;
 
-       if (set->timeout &&
+       if (timeout &&
            nla_put_be64(skb, NFTA_SET_TIMEOUT,
-                        nf_jiffies64_to_msecs(set->timeout),
+                        nf_jiffies64_to_msecs(timeout),
                         NFTA_SET_PAD))
                goto nla_put_failure;
-       if (set->gc_int &&
-           nla_put_be32(skb, NFTA_SET_GC_INTERVAL, htonl(set->gc_int)))
+       if (gc_int &&
+           nla_put_be32(skb, NFTA_SET_GC_INTERVAL, htonl(gc_int)))
                goto nla_put_failure;
 
        if (set->policy != NFT_SET_POL_PERFORMANCE) {
@@ -4487,7 +4501,10 @@ static int nf_tables_newset(struct sk_buff *skb, const struct nfnl_info *info,
                for (i = 0; i < num_exprs; i++)
                        nft_expr_destroy(&ctx, exprs[i]);
 
-               return err;
+               if (err < 0)
+                       return err;
+
+               return __nft_trans_set_add(&ctx, NFT_MSG_NEWSET, set, &desc);
        }
 
        if (!(info->nlh->nlmsg_flags & NLM_F_CREATE))
@@ -5877,7 +5894,7 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set,
                        return err;
        } else if (set->flags & NFT_SET_TIMEOUT &&
                   !(flags & NFT_SET_ELEM_INTERVAL_END)) {
-               timeout = set->timeout;
+               timeout = READ_ONCE(set->timeout);
        }
 
        expiration = 0;
@@ -5978,7 +5995,7 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set,
                if (err < 0)
                        goto err_parse_key_end;
 
-               if (timeout != set->timeout) {
+               if (timeout != READ_ONCE(set->timeout)) {
                        err = nft_set_ext_add(&tmpl, NFT_SET_EXT_TIMEOUT);
                        if (err < 0)
                                goto err_parse_key_end;
@@ -8833,14 +8850,20 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb)
                                nft_flow_rule_destroy(nft_trans_flow_rule(trans));
                        break;
                case NFT_MSG_NEWSET:
-                       nft_clear(net, nft_trans_set(trans));
-                       /* This avoids hitting -EBUSY when deleting the table
-                        * from the transaction.
-                        */
-                       if (nft_set_is_anonymous(nft_trans_set(trans)) &&
-                           !list_empty(&nft_trans_set(trans)->bindings))
-                               trans->ctx.table->use--;
+                       if (nft_trans_set_update(trans)) {
+                               struct nft_set *set = nft_trans_set(trans);
 
+                               WRITE_ONCE(set->timeout, nft_trans_set_timeout(trans));
+                               WRITE_ONCE(set->gc_int, nft_trans_set_gc_int(trans));
+                       } else {
+                               nft_clear(net, nft_trans_set(trans));
+                               /* This avoids hitting -EBUSY when deleting the table
+                                * from the transaction.
+                                */
+                               if (nft_set_is_anonymous(nft_trans_set(trans)) &&
+                                   !list_empty(&nft_trans_set(trans)->bindings))
+                                       trans->ctx.table->use--;
+                       }
                        nf_tables_set_notify(&trans->ctx, nft_trans_set(trans),
                                             NFT_MSG_NEWSET, GFP_KERNEL);
                        nft_trans_destroy(trans);
@@ -9062,6 +9085,10 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action)
                        nft_trans_destroy(trans);
                        break;
                case NFT_MSG_NEWSET:
+                       if (nft_trans_set_update(trans)) {
+                               nft_trans_destroy(trans);
+                               break;
+                       }
                        trans->ctx.table->use--;
                        if (nft_trans_set_bound(trans)) {
                                nft_trans_destroy(trans);