bpf: Harden register offset checks for release helpers and kfuncs
authorKumar Kartikeya Dwivedi <memxor@gmail.com>
Fri, 4 Mar 2022 22:46:41 +0000 (04:16 +0530)
committerAlexei Starovoitov <ast@kernel.org>
Sat, 5 Mar 2022 23:29:35 +0000 (15:29 -0800)
Let's ensure that the PTR_TO_BTF_ID reg being passed in to release BPF
helpers and kfuncs always has its offset set to 0. While not a real
problem now, there's a very real possibility this will become a problem
when more and more kfuncs are exposed, and more BPF helpers are added
which can release PTR_TO_BTF_ID.

Previous commits already protected against non-zero var_off. One of the
case we are concerned about now is when we have a type that can be
returned by e.g. an acquire kfunc:

struct foo {
int a;
int b;
struct bar b;
};

... and struct bar is also a type that can be returned by another
acquire kfunc.

Then, doing the following sequence:

struct foo *f = bpf_get_foo(); // acquire kfunc
if (!f)
return 0;
bpf_put_bar(&f->b); // release kfunc

... would work with the current code, since the btf_struct_ids_match
takes reg->off into account for matching pointer type with release kfunc
argument type, but would obviously be incorrect, and most likely lead to
a kernel crash. A test has been included later to prevent regressions in
this area.

Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Link: https://lore.kernel.org/bpf/20220304224645.3677453-5-memxor@gmail.com
include/linux/bpf_verifier.h
kernel/bpf/btf.c
kernel/bpf/verifier.c

index 38b24ee8d8c25786764a25c61436697204727172..c1fc4af47f696f79e6a5064d3940ee4c71bded89 100644 (file)
@@ -523,7 +523,8 @@ int check_ptr_off_reg(struct bpf_verifier_env *env,
                      const struct bpf_reg_state *reg, int regno);
 int check_func_arg_reg_off(struct bpf_verifier_env *env,
                           const struct bpf_reg_state *reg, int regno,
-                          enum bpf_arg_type arg_type);
+                          enum bpf_arg_type arg_type,
+                          bool is_release_func);
 int check_kfunc_mem_size_reg(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
                             u32 regno);
 int check_mem_reg(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
index 7f6a0ae5028ba5e1160bf25c64ffa1177ae61002..162807e3b4a5ea5c46e66f21272d96887b528cb3 100644 (file)
@@ -5753,6 +5753,10 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env,
                return -EINVAL;
        }
 
+       /* Only kfunc can be release func */
+       if (is_kfunc)
+               rel = btf_kfunc_id_set_contains(btf, resolve_prog_type(env->prog),
+                                               BTF_KFUNC_TYPE_RELEASE, func_id);
        /* check that BTF function arguments match actual types that the
         * verifier sees.
         */
@@ -5777,7 +5781,7 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env,
                ref_t = btf_type_skip_modifiers(btf, t->type, &ref_id);
                ref_tname = btf_name_by_offset(btf, ref_t->name_off);
 
-               ret = check_func_arg_reg_off(env, reg, regno, ARG_DONTCARE);
+               ret = check_func_arg_reg_off(env, reg, regno, ARG_DONTCARE, rel);
                if (ret < 0)
                        return ret;
 
@@ -5809,7 +5813,11 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env,
                        if (reg->type == PTR_TO_BTF_ID) {
                                reg_btf = reg->btf;
                                reg_ref_id = reg->btf_id;
-                               /* Ensure only one argument is referenced PTR_TO_BTF_ID */
+                               /* Ensure only one argument is referenced
+                                * PTR_TO_BTF_ID, check_func_arg_reg_off relies
+                                * on only one referenced register being allowed
+                                * for kfuncs.
+                                */
                                if (reg->ref_obj_id) {
                                        if (ref_obj_id) {
                                                bpf_log(log, "verifier internal error: more than one arg with ref_obj_id R%d %u %u\n",
@@ -5891,18 +5899,15 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env,
 
        /* Either both are set, or neither */
        WARN_ON_ONCE((ref_obj_id && !ref_regno) || (!ref_obj_id && ref_regno));
-       if (is_kfunc) {
-               rel = btf_kfunc_id_set_contains(btf, resolve_prog_type(env->prog),
-                                               BTF_KFUNC_TYPE_RELEASE, func_id);
-               /* We already made sure ref_obj_id is set only for one argument */
-               if (rel && !ref_obj_id) {
-                       bpf_log(log, "release kernel function %s expects refcounted PTR_TO_BTF_ID\n",
-                               func_name);
-                       return -EINVAL;
-               }
-               /* Allow (!rel && ref_obj_id), so that passing such referenced PTR_TO_BTF_ID to
-                * other kfuncs works
-                */
+       /* We already made sure ref_obj_id is set only for one argument. We do
+        * allow (!rel && ref_obj_id), so that passing such referenced
+        * PTR_TO_BTF_ID to other kfuncs works. Note that rel is only true when
+        * is_kfunc is true.
+        */
+       if (rel && !ref_obj_id) {
+               bpf_log(log, "release kernel function %s expects refcounted PTR_TO_BTF_ID\n",
+                       func_name);
+               return -EINVAL;
        }
        /* returns argument register number > 0 in case of reference release kfunc */
        return rel ? ref_regno : 0;
index 455b4ab69e47fb37f883fa11587f863fd19528e5..fe9a513e2314fdf49f1095b3ab65541964c60489 100644 (file)
@@ -5367,10 +5367,11 @@ found:
 
 int check_func_arg_reg_off(struct bpf_verifier_env *env,
                           const struct bpf_reg_state *reg, int regno,
-                          enum bpf_arg_type arg_type)
+                          enum bpf_arg_type arg_type,
+                          bool is_release_func)
 {
+       bool fixed_off_ok = false, release_reg;
        enum bpf_reg_type type = reg->type;
-       bool fixed_off_ok = false;
 
        switch ((u32)type) {
        case SCALAR_VALUE:
@@ -5395,6 +5396,21 @@ int check_func_arg_reg_off(struct bpf_verifier_env *env,
         * fixed offset.
         */
        case PTR_TO_BTF_ID:
+               /* When referenced PTR_TO_BTF_ID is passed to release function,
+                * it's fixed offset must be 0. We rely on the property that
+                * only one referenced register can be passed to BPF helpers and
+                * kfuncs. In the other cases, fixed offset can be non-zero.
+                */
+               release_reg = is_release_func && reg->ref_obj_id;
+               if (release_reg && reg->off) {
+                       verbose(env, "R%d must have zero offset when passed to release func\n",
+                               regno);
+                       return -EINVAL;
+               }
+               /* For release_reg == true, fixed_off_ok must be false, but we
+                * already checked and rejected reg->off != 0 above, so set to
+                * true to allow fixed offset for all other cases.
+                */
                fixed_off_ok = true;
                break;
        default:
@@ -5452,11 +5468,14 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
        if (err)
                return err;
 
-       err = check_func_arg_reg_off(env, reg, regno, arg_type);
+       err = check_func_arg_reg_off(env, reg, regno, arg_type, is_release_function(meta->func_id));
        if (err)
                return err;
 
 skip_type_check:
+       /* check_func_arg_reg_off relies on only one referenced register being
+        * allowed for BPF helpers.
+        */
        if (reg->ref_obj_id) {
                if (meta->ref_obj_id) {
                        verbose(env, "verifier internal error: more than one arg with ref_obj_id R%d %u %u\n",