netfilter: nf_tables: honor NLM_F_EXCL flag in set element insertion
authorPablo Neira Ayuso <pablo@netfilter.org>
Wed, 24 Aug 2016 10:41:54 +0000 (12:41 +0200)
committerPablo Neira Ayuso <pablo@netfilter.org>
Fri, 26 Aug 2016 15:30:20 +0000 (17:30 +0200)
If the NLM_F_EXCL flag is set, then new elements that clash with an
existing one return EEXIST. In case you try to add an element whose
data area differs from what we have, then this returns EBUSY. If no
flag is specified at all, then this returns success to userspace.

This patch also update the set insert operation so we can fetch the
existing element that clashes with the one you want to add, we need
this to make sure the element data doesn't differ.

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_hash.c
net/netfilter/nft_set_rbtree.c

index f2f13399ce44d6b36d851f25c50411cf9b3b6140..8972468bc94bc717a39e88a13298bf245f4bf855 100644 (file)
@@ -251,7 +251,8 @@ struct nft_set_ops {
 
        int                             (*insert)(const struct net *net,
                                                  const struct nft_set *set,
-                                                 const struct nft_set_elem *elem);
+                                                 const struct nft_set_elem *elem,
+                                                 struct nft_set_ext **ext);
        void                            (*activate)(const struct net *net,
                                                    const struct nft_set *set,
                                                    const struct nft_set_elem *elem);
index 221d27f09623da3d7fb1e91d0dd93d27f78d59a9..bd9715e5ff266202ab4c93f6475410d64e0ec9c5 100644 (file)
@@ -3483,12 +3483,12 @@ static int nft_setelem_parse_flags(const struct nft_set *set,
 }
 
 static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set,
-                           const struct nlattr *attr)
+                           const struct nlattr *attr, u32 nlmsg_flags)
 {
        struct nlattr *nla[NFTA_SET_ELEM_MAX + 1];
        struct nft_data_desc d1, d2;
        struct nft_set_ext_tmpl tmpl;
-       struct nft_set_ext *ext;
+       struct nft_set_ext *ext, *ext2;
        struct nft_set_elem elem;
        struct nft_set_binding *binding;
        struct nft_userdata *udata;
@@ -3615,9 +3615,19 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set,
                goto err4;
 
        ext->genmask = nft_genmask_cur(ctx->net) | NFT_SET_ELEM_BUSY_MASK;
-       err = set->ops->insert(ctx->net, set, &elem);
-       if (err < 0)
+       err = set->ops->insert(ctx->net, set, &elem, &ext2);
+       if (err) {
+               if (err == -EEXIST) {
+                       if (nft_set_ext_exists(ext, NFT_SET_EXT_DATA) &&
+                           nft_set_ext_exists(ext2, NFT_SET_EXT_DATA) &&
+                           memcmp(nft_set_ext_data(ext),
+                                  nft_set_ext_data(ext2), set->dlen) != 0)
+                               err = -EBUSY;
+                       else if (!(nlmsg_flags & NLM_F_EXCL))
+                               err = 0;
+               }
                goto err5;
+       }
 
        nft_trans_elem(trans) = elem;
        list_add_tail(&trans->list, &ctx->net->nft.commit_list);
@@ -3673,7 +3683,7 @@ static int nf_tables_newsetelem(struct net *net, struct sock *nlsk,
                    !atomic_add_unless(&set->nelems, 1, set->size + set->ndeact))
                        return -ENFILE;
 
-               err = nft_add_set_elem(&ctx, set, attr);
+               err = nft_add_set_elem(&ctx, set, attr, nlh->nlmsg_flags);
                if (err < 0) {
                        atomic_dec(&set->nelems);
                        break;
index 564fa7929ed5ad04bcf14951718f057b9aa82c3f..3794cb2fc78876ce02eb35992cb0113d498367ba 100644 (file)
@@ -126,7 +126,8 @@ err1:
 }
 
 static int nft_hash_insert(const struct net *net, const struct nft_set *set,
-                          const struct nft_set_elem *elem)
+                          const struct nft_set_elem *elem,
+                          struct nft_set_ext **ext)
 {
        struct nft_hash *priv = nft_set_priv(set);
        struct nft_hash_elem *he = elem->priv;
@@ -135,9 +136,17 @@ static int nft_hash_insert(const struct net *net, const struct nft_set *set,
                .set     = set,
                .key     = elem->key.val.data,
        };
-
-       return rhashtable_lookup_insert_key(&priv->ht, &arg, &he->node,
-                                           nft_hash_params);
+       struct nft_hash_elem *prev;
+
+       prev = rhashtable_lookup_get_insert_key(&priv->ht, &arg, &he->node,
+                                              nft_hash_params);
+       if (IS_ERR(prev))
+               return PTR_ERR(prev);
+       if (prev) {
+               *ext = &prev->ext;
+               return -EEXIST;
+       }
+       return 0;
 }
 
 static void nft_hash_activate(const struct net *net, const struct nft_set *set,
index 6473936d05c67aa08b7c507bb2bfd8c836bdfcce..038682d48261d10b2a100e97e8c2b4666915f791 100644 (file)
@@ -94,7 +94,8 @@ out:
 }
 
 static int __nft_rbtree_insert(const struct net *net, const struct nft_set *set,
-                              struct nft_rbtree_elem *new)
+                              struct nft_rbtree_elem *new,
+                              struct nft_set_ext **ext)
 {
        struct nft_rbtree *priv = nft_set_priv(set);
        u8 genmask = nft_genmask_next(net);
@@ -122,8 +123,10 @@ static int __nft_rbtree_insert(const struct net *net, const struct nft_set *set,
                                else if (!nft_rbtree_interval_end(rbe) &&
                                         nft_rbtree_interval_end(new))
                                        p = &parent->rb_right;
-                               else
+                               else {
+                                       *ext = &rbe->ext;
                                        return -EEXIST;
+                               }
                        }
                }
        }
@@ -133,13 +136,14 @@ static int __nft_rbtree_insert(const struct net *net, const struct nft_set *set,
 }
 
 static int nft_rbtree_insert(const struct net *net, const struct nft_set *set,
-                            const struct nft_set_elem *elem)
+                            const struct nft_set_elem *elem,
+                            struct nft_set_ext **ext)
 {
        struct nft_rbtree_elem *rbe = elem->priv;
        int err;
 
        spin_lock_bh(&nft_rbtree_lock);
-       err = __nft_rbtree_insert(net, set, rbe);
+       err = __nft_rbtree_insert(net, set, rbe, ext);
        spin_unlock_bh(&nft_rbtree_lock);
 
        return err;