bpf: Support constant scalar arguments for kfuncs
authorKumar Kartikeya Dwivedi <memxor@gmail.com>
Fri, 18 Nov 2022 01:56:02 +0000 (07:26 +0530)
committerAlexei Starovoitov <ast@kernel.org>
Fri, 18 Nov 2022 03:22:13 +0000 (19:22 -0800)
Allow passing known constant scalars as arguments to kfuncs that do not
represent a size parameter. We use mark_chain_precision for the constant
scalar argument to mark it precise. This makes the search pruning
optimization of verifier more conservative for such kfunc calls, and
each non-distinct argument is considered unequivalent.

We will use this support to then expose a bpf_obj_new function where it
takes the local type ID of a type in program BTF, and returns a
PTR_TO_BTF_ID | MEM_ALLOC to the local type, and allows programs to
allocate their own objects.

Each type ID resolves to a distinct type with a possibly distinct size,
hence the type ID constant matters in terms of program safety and its
precision needs to be checked between old and cur states inside regsafe.
The use of mark_chain_precision enables this.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Link: https://lore.kernel.org/r/20221118015614.2013203-13-memxor@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Documentation/bpf/kfuncs.rst
kernel/bpf/verifier.c

index 0f858156371de15d84631612dd812a339ac7d12a..3b1501c3b6cd08345b1a072d76916c3afc5e67ce 100644 (file)
@@ -72,6 +72,30 @@ argument as its size. By default, without __sz annotation, the size of the type
 of the pointer is used. Without __sz annotation, a kfunc cannot accept a void
 pointer.
 
+2.2.2 __k Annotation
+--------------------
+
+This annotation is only understood for scalar arguments, where it indicates that
+the verifier must check the scalar argument to be a known constant, which does
+not indicate a size parameter, and the value of the constant is relevant to the
+safety of the program.
+
+An example is given below::
+
+        void *bpf_obj_new(u32 local_type_id__k, ...)
+        {
+        ...
+        }
+
+Here, bpf_obj_new uses local_type_id argument to find out the size of that type
+ID in program's BTF and return a sized pointer to it. Each type ID will have a
+distinct size, hence it is crucial to treat each such call as distinct when
+values don't match during verifier state pruning checks.
+
+Hence, whenever a constant scalar argument is accepted by a kfunc which is not a
+size parameter, and the value of the constant matters for program safety, __k
+suffix should be used.
+
 .. _BPF_kfunc_nodef:
 
 2.3 Using an existing kernel function
index 7d7a91c54709dfd88732aa16655749fb1feb3ea4..c8fcc2808d999d6f8fd8d884a97f51eb30a4c2a4 100644 (file)
@@ -7875,6 +7875,10 @@ struct bpf_kfunc_call_arg_meta {
        u8 release_regno;
        bool r0_rdonly;
        u64 r0_size;
+       struct {
+               u64 value;
+               bool found;
+       } arg_constant;
 };
 
 static bool is_kfunc_acquire(struct bpf_kfunc_call_arg_meta *meta)
@@ -7912,30 +7916,40 @@ static bool is_kfunc_arg_kptr_get(struct bpf_kfunc_call_arg_meta *meta, int arg)
        return arg == 0 && (meta->kfunc_flags & KF_KPTR_GET);
 }
 
-static bool is_kfunc_arg_mem_size(const struct btf *btf,
-                                 const struct btf_param *arg,
-                                 const struct bpf_reg_state *reg)
+static bool __kfunc_param_match_suffix(const struct btf *btf,
+                                      const struct btf_param *arg,
+                                      const char *suffix)
 {
-       int len, sfx_len = sizeof("__sz") - 1;
-       const struct btf_type *t;
+       int suffix_len = strlen(suffix), len;
        const char *param_name;
 
-       t = btf_type_skip_modifiers(btf, arg->type, NULL);
-       if (!btf_type_is_scalar(t) || reg->type != SCALAR_VALUE)
-               return false;
-
        /* In the future, this can be ported to use BTF tagging */
        param_name = btf_name_by_offset(btf, arg->name_off);
        if (str_is_empty(param_name))
                return false;
        len = strlen(param_name);
-       if (len < sfx_len)
+       if (len < suffix_len)
                return false;
-       param_name += len - sfx_len;
-       if (strncmp(param_name, "__sz", sfx_len))
+       param_name += len - suffix_len;
+       return !strncmp(param_name, suffix, suffix_len);
+}
+
+static bool is_kfunc_arg_mem_size(const struct btf *btf,
+                                 const struct btf_param *arg,
+                                 const struct bpf_reg_state *reg)
+{
+       const struct btf_type *t;
+
+       t = btf_type_skip_modifiers(btf, arg->type, NULL);
+       if (!btf_type_is_scalar(t) || reg->type != SCALAR_VALUE)
                return false;
 
-       return true;
+       return __kfunc_param_match_suffix(btf, arg, "__sz");
+}
+
+static bool is_kfunc_arg_constant(const struct btf *btf, const struct btf_param *arg)
+{
+       return __kfunc_param_match_suffix(btf, arg, "__k");
 }
 
 static bool is_kfunc_arg_scalar_with_name(const struct btf *btf,
@@ -8205,7 +8219,22 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
                                verbose(env, "R%d is not a scalar\n", regno);
                                return -EINVAL;
                        }
-                       if (is_kfunc_arg_scalar_with_name(btf, &args[i], "rdonly_buf_size")) {
+
+                       if (is_kfunc_arg_constant(meta->btf, &args[i])) {
+                               if (meta->arg_constant.found) {
+                                       verbose(env, "verifier internal error: only one constant argument permitted\n");
+                                       return -EFAULT;
+                               }
+                               if (!tnum_is_const(reg->var_off)) {
+                                       verbose(env, "R%d must be a known constant\n", regno);
+                                       return -EINVAL;
+                               }
+                               ret = mark_chain_precision(env, regno);
+                               if (ret < 0)
+                                       return ret;
+                               meta->arg_constant.found = true;
+                               meta->arg_constant.value = reg->var_off.value;
+                       } else if (is_kfunc_arg_scalar_with_name(btf, &args[i], "rdonly_buf_size")) {
                                meta->r0_rdonly = true;
                                is_ret_buf_sz = true;
                        } else if (is_kfunc_arg_scalar_with_name(btf, &args[i], "rdwr_buf_size")) {