mirror of
https://github.com/torvalds/linux
synced 2024-09-06 18:01:05 +00:00
bpf: enforce exact retval range on subprog/callback exit
Instead of relying on potentially imprecise tnum representation of expected return value range for callbacks and subprogs, validate that smin/smax range satisfy exact expected range of return values. E.g., if callback would need to return [0, 2] range, tnum can't represent this precisely and instead will allow [0, 3] range. By checking smin/smax range, we can make sure that subprog/callback indeed returns only valid [0, 2] range. Acked-by: Eduard Zingerman <eddyz87@gmail.com> Acked-by: Shung-Hsi Yu <shung-hsi.yu@suse.com> Signed-off-by: Andrii Nakryiko <andrii@kernel.org> Link: https://lore.kernel.org/r/20231202175705.885270-5-andrii@kernel.org Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
parent
0acd03a5bd
commit
8fa4ecd49b
|
@ -275,6 +275,11 @@ struct bpf_reference_state {
|
|||
int callback_ref;
|
||||
};
|
||||
|
||||
struct bpf_retval_range {
|
||||
s32 minval;
|
||||
s32 maxval;
|
||||
};
|
||||
|
||||
/* state of the program:
|
||||
* type of all registers and stack info
|
||||
*/
|
||||
|
@ -297,7 +302,7 @@ struct bpf_func_state {
|
|||
* void foo(void) { bpf_timer_set_callback(,foo); }
|
||||
*/
|
||||
u32 async_entry_cnt;
|
||||
struct tnum callback_ret_range;
|
||||
struct bpf_retval_range callback_ret_range;
|
||||
bool in_callback_fn;
|
||||
bool in_async_callback_fn;
|
||||
bool in_exception_callback_fn;
|
||||
|
|
|
@ -2305,6 +2305,11 @@ static void init_reg_state(struct bpf_verifier_env *env,
|
|||
regs[BPF_REG_FP].frameno = state->frameno;
|
||||
}
|
||||
|
||||
static struct bpf_retval_range retval_range(s32 minval, s32 maxval)
|
||||
{
|
||||
return (struct bpf_retval_range){ minval, maxval };
|
||||
}
|
||||
|
||||
#define BPF_MAIN_FUNC (-1)
|
||||
static void init_func_state(struct bpf_verifier_env *env,
|
||||
struct bpf_func_state *state,
|
||||
|
@ -2313,7 +2318,7 @@ static void init_func_state(struct bpf_verifier_env *env,
|
|||
state->callsite = callsite;
|
||||
state->frameno = frameno;
|
||||
state->subprogno = subprogno;
|
||||
state->callback_ret_range = tnum_range(0, 0);
|
||||
state->callback_ret_range = retval_range(0, 0);
|
||||
init_reg_state(env, state);
|
||||
mark_verifier_state_scratched(env);
|
||||
}
|
||||
|
@ -9396,7 +9401,7 @@ static int set_map_elem_callback_state(struct bpf_verifier_env *env,
|
|||
return err;
|
||||
|
||||
callee->in_callback_fn = true;
|
||||
callee->callback_ret_range = tnum_range(0, 1);
|
||||
callee->callback_ret_range = retval_range(0, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -9418,7 +9423,7 @@ static int set_loop_callback_state(struct bpf_verifier_env *env,
|
|||
__mark_reg_not_init(env, &callee->regs[BPF_REG_5]);
|
||||
|
||||
callee->in_callback_fn = true;
|
||||
callee->callback_ret_range = tnum_range(0, 1);
|
||||
callee->callback_ret_range = retval_range(0, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -9448,7 +9453,7 @@ static int set_timer_callback_state(struct bpf_verifier_env *env,
|
|||
__mark_reg_not_init(env, &callee->regs[BPF_REG_4]);
|
||||
__mark_reg_not_init(env, &callee->regs[BPF_REG_5]);
|
||||
callee->in_async_callback_fn = true;
|
||||
callee->callback_ret_range = tnum_range(0, 1);
|
||||
callee->callback_ret_range = retval_range(0, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -9476,7 +9481,7 @@ static int set_find_vma_callback_state(struct bpf_verifier_env *env,
|
|||
__mark_reg_not_init(env, &callee->regs[BPF_REG_4]);
|
||||
__mark_reg_not_init(env, &callee->regs[BPF_REG_5]);
|
||||
callee->in_callback_fn = true;
|
||||
callee->callback_ret_range = tnum_range(0, 1);
|
||||
callee->callback_ret_range = retval_range(0, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -9499,7 +9504,7 @@ static int set_user_ringbuf_callback_state(struct bpf_verifier_env *env,
|
|||
__mark_reg_not_init(env, &callee->regs[BPF_REG_5]);
|
||||
|
||||
callee->in_callback_fn = true;
|
||||
callee->callback_ret_range = tnum_range(0, 1);
|
||||
callee->callback_ret_range = retval_range(0, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -9531,7 +9536,7 @@ static int set_rbtree_add_callback_state(struct bpf_verifier_env *env,
|
|||
__mark_reg_not_init(env, &callee->regs[BPF_REG_4]);
|
||||
__mark_reg_not_init(env, &callee->regs[BPF_REG_5]);
|
||||
callee->in_callback_fn = true;
|
||||
callee->callback_ret_range = tnum_range(0, 1);
|
||||
callee->callback_ret_range = retval_range(0, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -9560,6 +9565,11 @@ static bool in_rbtree_lock_required_cb(struct bpf_verifier_env *env)
|
|||
return is_rbtree_lock_required_kfunc(kfunc_btf_id);
|
||||
}
|
||||
|
||||
static bool retval_range_within(struct bpf_retval_range range, const struct bpf_reg_state *reg)
|
||||
{
|
||||
return range.minval <= reg->smin_value && reg->smax_value <= range.maxval;
|
||||
}
|
||||
|
||||
static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx)
|
||||
{
|
||||
struct bpf_verifier_state *state = env->cur_state, *prev_st;
|
||||
|
@ -9583,9 +9593,6 @@ static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx)
|
|||
|
||||
caller = state->frame[state->curframe - 1];
|
||||
if (callee->in_callback_fn) {
|
||||
/* enforce R0 return value range [0, 1]. */
|
||||
struct tnum range = callee->callback_ret_range;
|
||||
|
||||
if (r0->type != SCALAR_VALUE) {
|
||||
verbose(env, "R0 not a scalar value\n");
|
||||
return -EACCES;
|
||||
|
@ -9597,7 +9604,11 @@ static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx)
|
|||
if (err)
|
||||
return err;
|
||||
|
||||
if (!tnum_in(range, r0->var_off)) {
|
||||
/* enforce R0 return value range */
|
||||
if (!retval_range_within(callee->callback_ret_range, r0)) {
|
||||
struct tnum range = tnum_range(callee->callback_ret_range.minval,
|
||||
callee->callback_ret_range.maxval);
|
||||
|
||||
verbose_invalid_scalar(env, r0, &range, "callback return", "R0");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue