On Fri, Mar 04, 2022 at 05:35:04AM +0530, Kumar Kartikeya Dwivedi wrote: > 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@xxxxxxxxx> > --- > include/linux/bpf_verifier.h | 3 ++- > kernel/bpf/btf.c | 13 +++++++++---- > kernel/bpf/verifier.c | 27 +++++++++++++++++++++++---- > 3 files changed, 34 insertions(+), 9 deletions(-) > > diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h > index 38b24ee8d8c2..7a684050495a 100644 > --- a/include/linux/bpf_verifier.h > +++ b/include/linux/bpf_verifier.h > @@ -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_function); > 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, > diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c > index 7f6a0ae5028b..c9a1019dc60d 100644 > --- a/kernel/bpf/btf.c > +++ b/kernel/bpf/btf.c > @@ -5753,6 +5753,9 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, > return -EINVAL; > } > > + 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 +5780,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 +5812,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", > @@ -5892,8 +5899,6 @@ 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) { This test is no longer needed? > - 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", > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > index e55bfd23e81b..c31407d156e7 100644 > --- a/kernel/bpf/verifier.c > +++ b/kernel/bpf/verifier.c > @@ -5367,11 +5367,28 @@ static int check_reg_type(struct bpf_verifier_env *env, u32 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) > { > enum bpf_reg_type type = reg->type; > + bool fixed_off_ok = false; > int err; > > + /* 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. > + */ > + if (type == PTR_TO_BTF_ID) { > + bool release_reg = is_release_func && reg->ref_obj_id; > + > + if (release_reg && reg->off) { iiuc, the reason for not going through __check_ptr_off_reg() is because it prefers a different verifier log message for release_reg case for fixed off. How about var_off? > + verbose(env, "R%d must have zero offset when passed to release func\n", > + regno); > + return -EINVAL; > + } > + fixed_off_ok = release_reg ? false : true; nit. fixed_off_ok = !release_reg; but this is a bit moot here considering the reg->off check has already been done for the release_reg case. > + } > + > switch ((u32)type) { > case SCALAR_VALUE: > /* Pointer types where reg offset is explicitly allowed: */ > @@ -5394,8 +5411,7 @@ int check_func_arg_reg_off(struct bpf_verifier_env *env, > /* All the rest must be rejected: */ > default: > force_off_check: > - err = __check_ptr_off_reg(env, reg, regno, > - type == PTR_TO_BTF_ID); > + err = __check_ptr_off_reg(env, reg, regno, fixed_off_ok); > if (err < 0) > return err; > break; > @@ -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", > -- > 2.35.1 >