Commit 24d5bb80 authored by Kumar Kartikeya Dwivedi's avatar Kumar Kartikeya Dwivedi Committed by Alexei Starovoitov

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: default avatarKumar Kartikeya Dwivedi <memxor@gmail.com>
Signed-off-by: default avatarAlexei Starovoitov <ast@kernel.org>
Link: https://lore.kernel.org/bpf/20220304224645.3677453-5-memxor@gmail.com
parent e1fad0ff
...@@ -523,7 +523,8 @@ int check_ptr_off_reg(struct bpf_verifier_env *env, ...@@ -523,7 +523,8 @@ int check_ptr_off_reg(struct bpf_verifier_env *env,
const struct bpf_reg_state *reg, int regno); const struct bpf_reg_state *reg, int regno);
int check_func_arg_reg_off(struct bpf_verifier_env *env, int check_func_arg_reg_off(struct bpf_verifier_env *env,
const struct bpf_reg_state *reg, int regno, 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, int check_kfunc_mem_size_reg(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
u32 regno); u32 regno);
int check_mem_reg(struct bpf_verifier_env *env, struct bpf_reg_state *reg, int check_mem_reg(struct bpf_verifier_env *env, struct bpf_reg_state *reg,
......
...@@ -5753,6 +5753,10 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, ...@@ -5753,6 +5753,10 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env,
return -EINVAL; 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 /* check that BTF function arguments match actual types that the
* verifier sees. * verifier sees.
*/ */
...@@ -5777,7 +5781,7 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, ...@@ -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_t = btf_type_skip_modifiers(btf, t->type, &ref_id);
ref_tname = btf_name_by_offset(btf, ref_t->name_off); 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) if (ret < 0)
return ret; return ret;
...@@ -5809,7 +5813,11 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, ...@@ -5809,7 +5813,11 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env,
if (reg->type == PTR_TO_BTF_ID) { if (reg->type == PTR_TO_BTF_ID) {
reg_btf = reg->btf; reg_btf = reg->btf;
reg_ref_id = reg->btf_id; 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 (reg->ref_obj_id) {
if (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", bpf_log(log, "verifier internal error: more than one arg with ref_obj_id R%d %u %u\n",
...@@ -5891,19 +5899,16 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env, ...@@ -5891,19 +5899,16 @@ static int btf_check_func_arg_match(struct bpf_verifier_env *env,
/* Either both are set, or neither */ /* Either both are set, or neither */
WARN_ON_ONCE((ref_obj_id && !ref_regno) || (!ref_obj_id && ref_regno)); WARN_ON_ONCE((ref_obj_id && !ref_regno) || (!ref_obj_id && ref_regno));
if (is_kfunc) { /* We already made sure ref_obj_id is set only for one argument. We do
rel = btf_kfunc_id_set_contains(btf, resolve_prog_type(env->prog), * allow (!rel && ref_obj_id), so that passing such referenced
BTF_KFUNC_TYPE_RELEASE, func_id); * PTR_TO_BTF_ID to other kfuncs works. Note that rel is only true when
/* We already made sure ref_obj_id is set only for one argument */ * is_kfunc is true.
*/
if (rel && !ref_obj_id) { if (rel && !ref_obj_id) {
bpf_log(log, "release kernel function %s expects refcounted PTR_TO_BTF_ID\n", bpf_log(log, "release kernel function %s expects refcounted PTR_TO_BTF_ID\n",
func_name); func_name);
return -EINVAL; return -EINVAL;
} }
/* Allow (!rel && ref_obj_id), so that passing such referenced PTR_TO_BTF_ID to
* other kfuncs works
*/
}
/* returns argument register number > 0 in case of reference release kfunc */ /* returns argument register number > 0 in case of reference release kfunc */
return rel ? ref_regno : 0; return rel ? ref_regno : 0;
} }
......
...@@ -5367,10 +5367,11 @@ static int check_reg_type(struct bpf_verifier_env *env, u32 regno, ...@@ -5367,10 +5367,11 @@ static int check_reg_type(struct bpf_verifier_env *env, u32 regno,
int check_func_arg_reg_off(struct bpf_verifier_env *env, int check_func_arg_reg_off(struct bpf_verifier_env *env,
const struct bpf_reg_state *reg, int regno, 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; enum bpf_reg_type type = reg->type;
bool fixed_off_ok = false;
switch ((u32)type) { switch ((u32)type) {
case SCALAR_VALUE: case SCALAR_VALUE:
...@@ -5395,6 +5396,21 @@ int check_func_arg_reg_off(struct bpf_verifier_env *env, ...@@ -5395,6 +5396,21 @@ int check_func_arg_reg_off(struct bpf_verifier_env *env,
* fixed offset. * fixed offset.
*/ */
case PTR_TO_BTF_ID: 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; fixed_off_ok = true;
break; break;
default: default:
...@@ -5452,11 +5468,14 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg, ...@@ -5452,11 +5468,14 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
if (err) if (err)
return 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) if (err)
return err; return err;
skip_type_check: 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 (reg->ref_obj_id) {
if (meta->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", verbose(env, "verifier internal error: more than one arg with ref_obj_id R%d %u %u\n",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment