]> www.infradead.org Git - users/dwmw2/linux.git/commitdiff
netfilter: nf_tables: fix set size with rbtree backend
authorPablo Neira Ayuso <pablo@netfilter.org>
Mon, 6 Jan 2025 22:40:50 +0000 (23:40 +0100)
committerPablo Neira Ayuso <pablo@netfilter.org>
Sun, 19 Jan 2025 15:41:41 +0000 (16:41 +0100)
The existing rbtree implementation uses singleton elements to represent
ranges, however, userspace provides a set size according to the number
of ranges in the set.

Adjust provided userspace set size to the number of singleton elements
in the kernel by multiplying the range by two.

Check if the no-match all-zero element is already in the set, in such
case release one slot in the set size.

Fixes: 0ed6389c483d ("netfilter: nf_tables: rename set implementations")
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
include/net/netfilter/nf_tables.h
net/netfilter/nf_tables_api.c
net/netfilter/nft_set_rbtree.c

index 0027beca5cd5032f4fcc53523b2557a5151d8c0b..f6958118986ac49edf71d7a1ed6bee3dfb44447b 100644 (file)
@@ -442,6 +442,9 @@ struct nft_set_ext;
  *     @remove: remove element from set
  *     @walk: iterate over all set elements
  *     @get: get set elements
+ *     @ksize: kernel set size
+ *     @usize: userspace set size
+ *     @adjust_maxsize: delta to adjust maximum set size
  *     @commit: commit set elements
  *     @abort: abort set elements
  *     @privsize: function to return size of set private data
@@ -495,6 +498,9 @@ struct nft_set_ops {
                                               const struct nft_set *set,
                                               const struct nft_set_elem *elem,
                                               unsigned int flags);
+       u32                             (*ksize)(u32 size);
+       u32                             (*usize)(u32 size);
+       u32                             (*adjust_maxsize)(const struct nft_set *set);
        void                            (*commit)(struct nft_set *set);
        void                            (*abort)(const struct nft_set *set);
        u64                             (*privsize)(const struct nlattr * const nla[],
index 83f3face8bb3f2ca7b591570f415140ea2b98ba0..de9c4335ef476f6a5d1ba2e6f5ec77fa3e492100 100644 (file)
@@ -4752,6 +4752,14 @@ static int nf_tables_fill_set_concat(struct sk_buff *skb,
        return 0;
 }
 
+static u32 nft_set_userspace_size(const struct nft_set_ops *ops, u32 size)
+{
+       if (ops->usize)
+               return ops->usize(size);
+
+       return size;
+}
+
 static int nf_tables_fill_set(struct sk_buff *skb, const struct nft_ctx *ctx,
                              const struct nft_set *set, u16 event, u16 flags)
 {
@@ -4822,7 +4830,8 @@ static int nf_tables_fill_set(struct sk_buff *skb, const struct nft_ctx *ctx,
        if (!nest)
                goto nla_put_failure;
        if (set->size &&
-           nla_put_be32(skb, NFTA_SET_DESC_SIZE, htonl(set->size)))
+           nla_put_be32(skb, NFTA_SET_DESC_SIZE,
+                        htonl(nft_set_userspace_size(set->ops, set->size))))
                goto nla_put_failure;
 
        if (set->field_count > 1 &&
@@ -5190,6 +5199,15 @@ static bool nft_set_is_same(const struct nft_set *set,
        return true;
 }
 
+static u32 nft_set_kernel_size(const struct nft_set_ops *ops,
+                              const struct nft_set_desc *desc)
+{
+       if (ops->ksize)
+               return ops->ksize(desc->size);
+
+       return desc->size;
+}
+
 static int nf_tables_newset(struct sk_buff *skb, const struct nfnl_info *info,
                            const struct nlattr * const nla[])
 {
@@ -5372,6 +5390,9 @@ static int nf_tables_newset(struct sk_buff *skb, const struct nfnl_info *info,
                if (err < 0)
                        return err;
 
+               if (desc.size)
+                       desc.size = nft_set_kernel_size(set->ops, &desc);
+
                err = 0;
                if (!nft_set_is_same(set, &desc, exprs, num_exprs, flags)) {
                        NL_SET_BAD_ATTR(extack, nla[NFTA_SET_NAME]);
@@ -5394,6 +5415,9 @@ static int nf_tables_newset(struct sk_buff *skb, const struct nfnl_info *info,
        if (IS_ERR(ops))
                return PTR_ERR(ops);
 
+       if (desc.size)
+               desc.size = nft_set_kernel_size(ops, &desc);
+
        udlen = 0;
        if (nla[NFTA_SET_USERDATA])
                udlen = nla_len(nla[NFTA_SET_USERDATA]);
@@ -7050,6 +7074,27 @@ static bool nft_setelem_valid_key_end(const struct nft_set *set,
        return true;
 }
 
+static u32 nft_set_maxsize(const struct nft_set *set)
+{
+       u32 maxsize, delta;
+
+       if (!set->size)
+               return UINT_MAX;
+
+       if (set->ops->adjust_maxsize)
+               delta = set->ops->adjust_maxsize(set);
+       else
+               delta = 0;
+
+       if (check_add_overflow(set->size, set->ndeact, &maxsize))
+               return UINT_MAX;
+
+       if (check_add_overflow(maxsize, delta, &maxsize))
+               return UINT_MAX;
+
+       return maxsize;
+}
+
 static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set,
                            const struct nlattr *attr, u32 nlmsg_flags)
 {
@@ -7422,7 +7467,7 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set,
        }
 
        if (!(flags & NFT_SET_ELEM_CATCHALL)) {
-               unsigned int max = set->size ? set->size + set->ndeact : UINT_MAX;
+               unsigned int max = nft_set_maxsize(set);
 
                if (!atomic_add_unless(&set->nelems, 1, max)) {
                        err = -ENFILE;
index b7ea21327549b353c087b3e607e722f391ea94c1..2e8ef16ff191d42a47b86a64e929d6fdad3e1716 100644 (file)
@@ -750,6 +750,46 @@ static void nft_rbtree_gc_init(const struct nft_set *set)
        priv->last_gc = jiffies;
 }
 
+/* rbtree stores ranges as singleton elements, each range is composed of two
+ * elements ...
+ */
+static u32 nft_rbtree_ksize(u32 size)
+{
+       return size * 2;
+}
+
+/* ... hide this detail to userspace. */
+static u32 nft_rbtree_usize(u32 size)
+{
+       if (!size)
+               return 0;
+
+       return size / 2;
+}
+
+static u32 nft_rbtree_adjust_maxsize(const struct nft_set *set)
+{
+       struct nft_rbtree *priv = nft_set_priv(set);
+       struct nft_rbtree_elem *rbe;
+       struct rb_node *node;
+       const void *key;
+
+       node = rb_last(&priv->root);
+       if (!node)
+               return 0;
+
+       rbe = rb_entry(node, struct nft_rbtree_elem, node);
+       if (!nft_rbtree_interval_end(rbe))
+               return 0;
+
+       key = nft_set_ext_key(&rbe->ext);
+       if (memchr(key, 1, set->klen))
+               return 0;
+
+       /* this is the all-zero no-match element. */
+       return 1;
+}
+
 const struct nft_set_type nft_set_rbtree_type = {
        .features       = NFT_SET_INTERVAL | NFT_SET_MAP | NFT_SET_OBJECT | NFT_SET_TIMEOUT,
        .ops            = {
@@ -768,5 +808,8 @@ const struct nft_set_type nft_set_rbtree_type = {
                .lookup         = nft_rbtree_lookup,
                .walk           = nft_rbtree_walk,
                .get            = nft_rbtree_get,
+               .ksize          = nft_rbtree_ksize,
+               .usize          = nft_rbtree_usize,
+               .adjust_maxsize = nft_rbtree_adjust_maxsize,
        },
 };