bpf: Harden register offset checks for release helpers and kfuncs
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
This commit is contained in:
committed by
Alexei Starovoitov
parent
e1fad0ff46
commit
24d5bb806c
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user