bpf: enforce exact retval range on subprog/callback exit
authorAndrii Nakryiko <andrii@kernel.org>
Sat, 2 Dec 2023 17:56:58 +0000 (09:56 -0800)
committerAlexei Starovoitov <ast@kernel.org>
Sat, 2 Dec 2023 19:36:50 +0000 (11:36 -0800)
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>
include/linux/bpf_verifier.h
kernel/bpf/verifier.c

index 0c0e1bccad45d2b57886a5ddc9bd2bc65d93b874..3378cc753061e6293737e7833c23ab1e388e3864 100644 (file)
@@ -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;
index 849fbf47b5f352cf5dc04a791c42b768a3c369ef..f3d9d7de68da594e57186298193a6bf7f345f144 100644 (file)
@@ -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;
                }