netfilter: nf_tables: switch registers to 32 bit addressing
[linux-block.git] / net / netfilter / nf_tables_api.c
index 3aa92b3f85fd075819bd1f34972a1f1c4de05244..03faf76ce3b868a17d16db9d038593349633f1d6 100644 (file)
@@ -2159,7 +2159,7 @@ nft_select_set_ops(const struct nlattr * const nla[],
        features = 0;
        if (nla[NFTA_SET_FLAGS] != NULL) {
                features = ntohl(nla_get_be32(nla[NFTA_SET_FLAGS]));
-               features &= NFT_SET_INTERVAL | NFT_SET_MAP;
+               features &= NFT_SET_INTERVAL | NFT_SET_MAP | NFT_SET_TIMEOUT;
        }
 
        bops       = NULL;
@@ -2797,9 +2797,10 @@ static int nf_tables_bind_check_setelem(const struct nft_ctx *ctx,
        enum nft_registers dreg;
 
        dreg = nft_type_to_reg(set->dtype);
-       return nft_validate_data_load(ctx, dreg, nft_set_ext_data(ext),
-                                     set->dtype == NFT_DATA_VERDICT ?
-                                     NFT_DATA_VERDICT : NFT_DATA_VALUE);
+       return nft_validate_register_store(ctx, dreg, nft_set_ext_data(ext),
+                                          set->dtype == NFT_DATA_VERDICT ?
+                                          NFT_DATA_VERDICT : NFT_DATA_VALUE,
+                                          set->dlen);
 }
 
 int nf_tables_bind_set(const struct nft_ctx *ctx, struct nft_set *set,
@@ -2811,12 +2812,13 @@ int nf_tables_bind_set(const struct nft_ctx *ctx, struct nft_set *set,
        if (!list_empty(&set->bindings) && set->flags & NFT_SET_ANONYMOUS)
                return -EBUSY;
 
-       if (set->flags & NFT_SET_MAP) {
+       if (binding->flags & NFT_SET_MAP) {
                /* If the set is already bound to the same chain all
                 * jumps are already validated for that chain.
                 */
                list_for_each_entry(i, &set->bindings, list) {
-                       if (i->chain == binding->chain)
+                       if (binding->flags & NFT_SET_MAP &&
+                           i->chain == binding->chain)
                                goto bind;
                }
 
@@ -2871,6 +2873,10 @@ const struct nft_set_ext_type nft_set_ext_types[] = {
                .len    = sizeof(unsigned long),
                .align  = __alignof__(unsigned long),
        },
+       [NFT_SET_EXT_USERDATA]          = {
+               .len    = sizeof(struct nft_userdata),
+               .align  = __alignof__(struct nft_userdata),
+       },
 };
 EXPORT_SYMBOL_GPL(nft_set_ext_types);
 
@@ -2883,6 +2889,8 @@ static const struct nla_policy nft_set_elem_policy[NFTA_SET_ELEM_MAX + 1] = {
        [NFTA_SET_ELEM_DATA]            = { .type = NLA_NESTED },
        [NFTA_SET_ELEM_FLAGS]           = { .type = NLA_U32 },
        [NFTA_SET_ELEM_TIMEOUT]         = { .type = NLA_U64 },
+       [NFTA_SET_ELEM_USERDATA]        = { .type = NLA_BINARY,
+                                           .len = NFT_USERDATA_MAXLEN },
 };
 
 static const struct nla_policy nft_set_elem_list_policy[NFTA_SET_ELEM_LIST_MAX + 1] = {
@@ -2963,6 +2971,15 @@ static int nf_tables_fill_setelem(struct sk_buff *skb,
                        goto nla_put_failure;
        }
 
+       if (nft_set_ext_exists(ext, NFT_SET_EXT_USERDATA)) {
+               struct nft_userdata *udata;
+
+               udata = nft_set_ext_userdata(ext);
+               if (nla_put(skb, NFTA_SET_ELEM_USERDATA,
+                           udata->len + 1, udata->data))
+                       goto nla_put_failure;
+       }
+
        nla_nest_end(skb, nest);
        return 0;
 
@@ -3182,11 +3199,10 @@ static struct nft_trans *nft_trans_elem_alloc(struct nft_ctx *ctx,
        return trans;
 }
 
-static void *nft_set_elem_init(const struct nft_set *set,
-                              const struct nft_set_ext_tmpl *tmpl,
-                              const struct nft_data *key,
-                              const struct nft_data *data,
-                              u64 timeout, gfp_t gfp)
+void *nft_set_elem_init(const struct nft_set *set,
+                       const struct nft_set_ext_tmpl *tmpl,
+                       const u32 *key, const u32 *data,
+                       u64 timeout, gfp_t gfp)
 {
        struct nft_set_ext *ext;
        void *elem;
@@ -3231,16 +3247,15 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set,
        struct nft_set_ext *ext;
        struct nft_set_elem elem;
        struct nft_set_binding *binding;
+       struct nft_userdata *udata;
        struct nft_data data;
        enum nft_registers dreg;
        struct nft_trans *trans;
        u64 timeout;
        u32 flags;
+       u8 ulen;
        int err;
 
-       if (set->size && set->nelems == set->size)
-               return -ENFILE;
-
        err = nla_parse_nested(nla, NFTA_SET_ELEM_MAX, attr,
                               nft_set_elem_policy);
        if (err < 0)
@@ -3315,8 +3330,12 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set,
                                .chain  = (struct nft_chain *)binding->chain,
                        };
 
-                       err = nft_validate_data_load(&bind_ctx, dreg,
-                                                    &data, d2.type);
+                       if (!(binding->flags & NFT_SET_MAP))
+                               continue;
+
+                       err = nft_validate_register_store(&bind_ctx, dreg,
+                                                         &data,
+                                                         d2.type, d2.len);
                        if (err < 0)
                                goto err3;
                }
@@ -3324,8 +3343,20 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set,
                nft_set_ext_add(&tmpl, NFT_SET_EXT_DATA);
        }
 
+       /* The full maximum length of userdata can exceed the maximum
+        * offset value (U8_MAX) for following extensions, therefor it
+        * must be the last extension added.
+        */
+       ulen = 0;
+       if (nla[NFTA_SET_ELEM_USERDATA] != NULL) {
+               ulen = nla_len(nla[NFTA_SET_ELEM_USERDATA]);
+               if (ulen > 0)
+                       nft_set_ext_add_length(&tmpl, NFT_SET_EXT_USERDATA,
+                                              ulen);
+       }
+
        err = -ENOMEM;
-       elem.priv = nft_set_elem_init(set, &tmpl, &elem.key, &data,
+       elem.priv = nft_set_elem_init(set, &tmpl, elem.key.data, data.data,
                                      timeout, GFP_KERNEL);
        if (elem.priv == NULL)
                goto err3;
@@ -3333,6 +3364,11 @@ static int nft_add_set_elem(struct nft_ctx *ctx, struct nft_set *set,
        ext = nft_set_elem_ext(set, elem.priv);
        if (flags)
                *nft_set_ext_flags(ext) = flags;
+       if (ulen > 0) {
+               udata = nft_set_ext_userdata(ext);
+               udata->len = ulen - 1;
+               nla_memcpy(&udata->data, nla[NFTA_SET_ELEM_USERDATA], ulen);
+       }
 
        trans = nft_trans_elem_alloc(ctx, NFT_MSG_NEWSETELEM, set);
        if (trans == NULL)
@@ -3391,11 +3427,15 @@ static int nf_tables_newsetelem(struct sock *nlsk, struct sk_buff *skb,
                return -EBUSY;
 
        nla_for_each_nested(attr, nla[NFTA_SET_ELEM_LIST_ELEMENTS], rem) {
+               if (set->size &&
+                   !atomic_add_unless(&set->nelems, 1, set->size + set->ndeact))
+                       return -ENFILE;
+
                err = nft_add_set_elem(&ctx, set, attr);
-               if (err < 0)
+               if (err < 0) {
+                       atomic_dec(&set->nelems);
                        break;
-
-               set->nelems++;
+               }
        }
        return err;
 }
@@ -3477,7 +3517,7 @@ static int nf_tables_delsetelem(struct sock *nlsk, struct sk_buff *skb,
                if (err < 0)
                        break;
 
-               set->nelems--;
+               set->ndeact++;
        }
        return err;
 }
@@ -3810,6 +3850,8 @@ static int nf_tables_commit(struct sk_buff *skb)
                                                 &te->elem,
                                                 NFT_MSG_DELSETELEM, 0);
                        te->set->ops->remove(te->set, &te->elem);
+                       atomic_dec(&te->set->nelems);
+                       te->set->ndeact--;
                        break;
                }
        }
@@ -3913,16 +3955,16 @@ static int nf_tables_abort(struct sk_buff *skb)
                        nft_trans_destroy(trans);
                        break;
                case NFT_MSG_NEWSETELEM:
-                       nft_trans_elem_set(trans)->nelems--;
                        te = (struct nft_trans_elem *)trans->data;
 
                        te->set->ops->remove(te->set, &te->elem);
+                       atomic_dec(&te->set->nelems);
                        break;
                case NFT_MSG_DELSETELEM:
                        te = (struct nft_trans_elem *)trans->data;
 
-                       nft_trans_elem_set(trans)->nelems++;
                        te->set->ops->activate(te->set, &te->elem);
+                       te->set->ndeact--;
 
                        nft_trans_destroy(trans);
                        break;
@@ -4006,10 +4048,10 @@ static int nf_tables_loop_check_setelem(const struct nft_ctx *ctx,
                return 0;
 
        data = nft_set_ext_data(ext);
-       switch (data->verdict) {
+       switch (data->verdict.code) {
        case NFT_JUMP:
        case NFT_GOTO:
-               return nf_tables_check_loops(ctx, data->chain);
+               return nf_tables_check_loops(ctx, data->verdict.chain);
        default:
                return 0;
        }
@@ -4042,10 +4084,11 @@ static int nf_tables_check_loops(const struct nft_ctx *ctx,
                        if (data == NULL)
                                continue;
 
-                       switch (data->verdict) {
+                       switch (data->verdict.code) {
                        case NFT_JUMP:
                        case NFT_GOTO:
-                               err = nf_tables_check_loops(ctx, data->chain);
+                               err = nf_tables_check_loops(ctx,
+                                                       data->verdict.chain);
                                if (err < 0)
                                        return err;
                        default:
@@ -4060,7 +4103,8 @@ static int nf_tables_check_loops(const struct nft_ctx *ctx,
                        continue;
 
                list_for_each_entry(binding, &set->bindings, list) {
-                       if (binding->chain != chain)
+                       if (!(binding->flags & NFT_SET_MAP) ||
+                           binding->chain != chain)
                                continue;
 
                        iter.skip       = 0;
@@ -4078,85 +4122,129 @@ static int nf_tables_check_loops(const struct nft_ctx *ctx,
 }
 
 /**
- *     nft_validate_input_register - validate an expressions' input register
+ *     nft_parse_register - parse a register value from a netlink attribute
  *
- *     @reg: the register number
+ *     @attr: netlink attribute
  *
- *     Validate that the input register is one of the general purpose
- *     registers.
+ *     Parse and translate a register value from a netlink attribute.
+ *     Registers used to be 128 bit wide, these register numbers will be
+ *     mapped to the corresponding 32 bit register numbers.
  */
-int nft_validate_input_register(enum nft_registers reg)
+unsigned int nft_parse_register(const struct nlattr *attr)
 {
-       if (reg <= NFT_REG_VERDICT)
-               return -EINVAL;
-       if (reg > NFT_REG_MAX)
-               return -ERANGE;
-       return 0;
+       unsigned int reg;
+
+       reg = ntohl(nla_get_be32(attr));
+       switch (reg) {
+       case NFT_REG_VERDICT...NFT_REG_4:
+               return reg * NFT_REG_SIZE / NFT_REG32_SIZE;
+       default:
+               return reg + NFT_REG_SIZE / NFT_REG32_SIZE - NFT_REG32_00;
+       }
 }
-EXPORT_SYMBOL_GPL(nft_validate_input_register);
+EXPORT_SYMBOL_GPL(nft_parse_register);
 
 /**
- *     nft_validate_output_register - validate an expressions' output register
+ *     nft_dump_register - dump a register value to a netlink attribute
+ *
+ *     @skb: socket buffer
+ *     @attr: attribute number
+ *     @reg: register number
+ *
+ *     Construct a netlink attribute containing the register number. For
+ *     compatibility reasons, register numbers being a multiple of 4 are
+ *     translated to the corresponding 128 bit register numbers.
+ */
+int nft_dump_register(struct sk_buff *skb, unsigned int attr, unsigned int reg)
+{
+       if (reg % (NFT_REG_SIZE / NFT_REG32_SIZE) == 0)
+               reg = reg / (NFT_REG_SIZE / NFT_REG32_SIZE);
+       else
+               reg = reg - NFT_REG_SIZE / NFT_REG32_SIZE + NFT_REG32_00;
+
+       return nla_put_be32(skb, attr, htonl(reg));
+}
+EXPORT_SYMBOL_GPL(nft_dump_register);
+
+/**
+ *     nft_validate_register_load - validate a load from a register
  *
  *     @reg: the register number
+ *     @len: the length of the data
  *
- *     Validate that the output register is one of the general purpose
- *     registers or the verdict register.
+ *     Validate that the input register is one of the general purpose
+ *     registers and that the length of the load is within the bounds.
  */
-int nft_validate_output_register(enum nft_registers reg)
+int nft_validate_register_load(enum nft_registers reg, unsigned int len)
 {
-       if (reg < NFT_REG_VERDICT)
+       if (reg < NFT_REG_1 * NFT_REG_SIZE / NFT_REG32_SIZE)
                return -EINVAL;
-       if (reg > NFT_REG_MAX)
+       if (len == 0)
+               return -EINVAL;
+       if (reg * NFT_REG32_SIZE + len > FIELD_SIZEOF(struct nft_regs, data))
                return -ERANGE;
+
        return 0;
 }
-EXPORT_SYMBOL_GPL(nft_validate_output_register);
+EXPORT_SYMBOL_GPL(nft_validate_register_load);
 
 /**
- *     nft_validate_data_load - validate an expressions' data load
+ *     nft_validate_register_store - validate an expressions' register store
  *
  *     @ctx: context of the expression performing the load
  *     @reg: the destination register number
  *     @data: the data to load
  *     @type: the data type
+ *     @len: the length of the data
  *
  *     Validate that a data load uses the appropriate data type for
- *     the destination register. A value of NULL for the data means
- *     that its runtime gathered data, which is always of type
- *     NFT_DATA_VALUE.
+ *     the destination register and the length is within the bounds.
+ *     A value of NULL for the data means that its runtime gathered
+ *     data.
  */
-int nft_validate_data_load(const struct nft_ctx *ctx, enum nft_registers reg,
-                          const struct nft_data *data,
-                          enum nft_data_types type)
+int nft_validate_register_store(const struct nft_ctx *ctx,
+                               enum nft_registers reg,
+                               const struct nft_data *data,
+                               enum nft_data_types type, unsigned int len)
 {
        int err;
 
        switch (reg) {
        case NFT_REG_VERDICT:
-               if (data == NULL || type != NFT_DATA_VERDICT)
+               if (type != NFT_DATA_VERDICT)
                        return -EINVAL;
 
-               if (data->verdict == NFT_GOTO || data->verdict == NFT_JUMP) {
-                       err = nf_tables_check_loops(ctx, data->chain);
+               if (data != NULL &&
+                   (data->verdict.code == NFT_GOTO ||
+                    data->verdict.code == NFT_JUMP)) {
+                       err = nf_tables_check_loops(ctx, data->verdict.chain);
                        if (err < 0)
                                return err;
 
-                       if (ctx->chain->level + 1 > data->chain->level) {
+                       if (ctx->chain->level + 1 >
+                           data->verdict.chain->level) {
                                if (ctx->chain->level + 1 == NFT_JUMP_STACK_SIZE)
                                        return -EMLINK;
-                               data->chain->level = ctx->chain->level + 1;
+                               data->verdict.chain->level = ctx->chain->level + 1;
                        }
                }
 
                return 0;
        default:
+               if (reg < NFT_REG_1 * NFT_REG_SIZE / NFT_REG32_SIZE)
+                       return -EINVAL;
+               if (len == 0)
+                       return -EINVAL;
+               if (reg * NFT_REG32_SIZE + len >
+                   FIELD_SIZEOF(struct nft_regs, data))
+                       return -ERANGE;
+
                if (data != NULL && type != NFT_DATA_VALUE)
                        return -EINVAL;
                return 0;
        }
 }
-EXPORT_SYMBOL_GPL(nft_validate_data_load);
+EXPORT_SYMBOL_GPL(nft_validate_register_store);
 
 static const struct nla_policy nft_verdict_policy[NFTA_VERDICT_MAX + 1] = {
        [NFTA_VERDICT_CODE]     = { .type = NLA_U32 },
@@ -4177,11 +4265,11 @@ static int nft_verdict_init(const struct nft_ctx *ctx, struct nft_data *data,
 
        if (!tb[NFTA_VERDICT_CODE])
                return -EINVAL;
-       data->verdict = ntohl(nla_get_be32(tb[NFTA_VERDICT_CODE]));
+       data->verdict.code = ntohl(nla_get_be32(tb[NFTA_VERDICT_CODE]));
 
-       switch (data->verdict) {
+       switch (data->verdict.code) {
        default:
-               switch (data->verdict & NF_VERDICT_MASK) {
+               switch (data->verdict.code & NF_VERDICT_MASK) {
                case NF_ACCEPT:
                case NF_DROP:
                case NF_QUEUE:
@@ -4207,7 +4295,7 @@ static int nft_verdict_init(const struct nft_ctx *ctx, struct nft_data *data,
                        return -EOPNOTSUPP;
 
                chain->use++;
-               data->chain = chain;
+               data->verdict.chain = chain;
                desc->len = sizeof(data);
                break;
        }
@@ -4218,10 +4306,10 @@ static int nft_verdict_init(const struct nft_ctx *ctx, struct nft_data *data,
 
 static void nft_verdict_uninit(const struct nft_data *data)
 {
-       switch (data->verdict) {
+       switch (data->verdict.code) {
        case NFT_JUMP:
        case NFT_GOTO:
-               data->chain->use--;
+               data->verdict.chain->use--;
                break;
        }
 }
@@ -4234,13 +4322,14 @@ static int nft_verdict_dump(struct sk_buff *skb, const struct nft_data *data)
        if (!nest)
                goto nla_put_failure;
 
-       if (nla_put_be32(skb, NFTA_VERDICT_CODE, htonl(data->verdict)))
+       if (nla_put_be32(skb, NFTA_VERDICT_CODE, htonl(data->verdict.code)))
                goto nla_put_failure;
 
-       switch (data->verdict) {
+       switch (data->verdict.code) {
        case NFT_JUMP:
        case NFT_GOTO:
-               if (nla_put_string(skb, NFTA_VERDICT_CHAIN, data->chain->name))
+               if (nla_put_string(skb, NFTA_VERDICT_CHAIN,
+                                  data->verdict.chain->name))
                        goto nla_put_failure;
        }
        nla_nest_end(skb, nest);