Re: [PATCH v3 bpf-next] bpf: Refactor release_regno searching logic

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On 2/14/23 4:13 PM, Joanne Koong wrote:
> On Tue, Feb 14, 2023 at 11:17 AM Dave Marchevsky <davemarchevsky@xxxxxx> wrote:
>>
>> Currently the ref_obj_id and OBJ_RELEASE searching is done in the code
>> that examines each individual arg (check_func_arg for helpers and
>> check_kfunc_args inner loop for kfuncs). This patch pulls out this
>> searching to occur before individual arg type handling, resulting in a
>> cleaner separation of logic and shared logic between kfuncs and helpers.
>>
>> The logic for this searching is already very similar between kfuncs and
>> helpers:
>>
>> Kfuncs:
>>   * Function-level KF_RELEASE flag indicates that the kfunc releases
>>     some previously-acquired arg
>>   * Verifier searches through arg regs to find those with ref_obj_id set
>>     * One such arg reg is selected. If multiple arg regs have ref_obj_id
>>       set, the last one (by regno) is chosen to be released
>>
>> Helpers:
>>   * OBJ_RELEASE is used in function proto to tag a particular arg as the
>>     one that should be released
>>     * There can only be one such tagged arg
>>   * Verifier confirms that only one arg reg has ref_obj_id set and that
>>     that reg matches expected OBJ_RELEASE arg
>>     * If OBJ_RELEASE arg doesn't have a matching ref_obj_id arg reg, or
>>       some other arg reg has ref_obj_id, it's an invalid state
>>
>> It's a long-term goal to merge as much kfunc and helper logic as
>> possible. Merging the similar functionality here is a small step in that
>> direction.
>>
>> Two new helper functions are added:
>>   * args_find_ref_obj_id_regno
>>     * For helpers and kfuncs. Searches through arg regs to find
>>       ref_obj_id reg and returns its regno.
>>
>>   * helper_proto_find_release_arg_regno
>>     * For helpers only. Searches through fn proto args to find the
>>       OBJ_RELEASE arg and returns the corresponding regno.
>>
>> The refactoring strives to keep failure logic and error messages
>> unchanged. However, because the release arg searching is now done before
>> any arg-specific type checking, verifier states that are invalid due to
>> both invalid release arg state _and_ some type- or helper-specific
>> checking logic might see the release arg-related error message first,
>> when previously verification would fail for the other reason.
>>
>> Signed-off-by: Dave Marchevsky <davemarchevsky@xxxxxx>
>> ---
>> v2 -> v3:
>>  * Edit patch summary for clarity
>>  * Correct err_multi comment in args_find_ref_obj_id_regno doc string
>>  * Rebase onto latest bpf-next: 'Revert "bpf: Add --skip_encoding_btf_inconsistent_proto, --btf_gen_optimized to pahole flags for v1.25"'
>>
>> v1 -> v2: https://lore.kernel.org/bpf/20230121002417.1684602-1-davemarchevsky@xxxxxx/
>>  * Fix uninitialized variable complaint (kernel test bot)
>>  * Add err_multi param to args_find_ref_obj_id_regno - kfunc arg reg
>>    checking wasn't erroring if multiple ref_obj_id arg regs were found,
>>    retain this behavior
>>
>> v0 -> v1: https://lore.kernel.org/bpf/20221217082506.1570898-2-davemarchevsky@xxxxxx/
>>  * Remove allow_multi from args_find_ref_obj_id_regno, no need to
>>    support multiple ref_obj_id arg regs
>>  * No need to use temp variable 'i' to count nargs (David)
>>  * Proper formatting of function-level comments on newly-added helpers (David)
>>
>>  kernel/bpf/verifier.c | 220 +++++++++++++++++++++++++++++-------------
>>  1 file changed, 153 insertions(+), 67 deletions(-)
>>
>> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
>> index 21e08c111702..c0d01085f44f 100644
>> --- a/kernel/bpf/verifier.c
>> +++ b/kernel/bpf/verifier.c
>> @@ -6735,48 +6735,6 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
>>                 return err;
>>
> [...]
>>  static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
>>                              int *insn_idx_p)
>>  {
>>         enum bpf_prog_type prog_type = resolve_prog_type(env->prog);
>> +       int i, err, func_id, nargs, release_regno, ref_regno;
>>         const struct bpf_func_proto *fn = NULL;
>>         enum bpf_return_type ret_type;
>>         enum bpf_type_flag ret_flag;
>> @@ -8115,7 +8178,6 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
>>         struct bpf_call_arg_meta meta;
>>         int insn_idx = *insn_idx_p;
>>         bool changes_data;
>> -       int i, err, func_id;
>>
>>         /* find function prototype */
>>         func_id = insn->imm;
>> @@ -8179,8 +8241,37 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
>>         }
>>
>>         meta.func_id = func_id;
>> +       regs = cur_regs(env);
>> +
>> +       /* find actual arg count */
>> +       for (nargs = 0; nargs < MAX_BPF_FUNC_REG_ARGS; nargs++)
>> +               if (fn->arg_type[nargs] == ARG_DONTCARE)
>> +                       break;
>> +
>> +       release_regno = helper_proto_find_release_arg_regno(env, fn, nargs);
>> +       if (release_regno < 0)
>> +               return release_regno;
>> +
>> +       ref_regno = args_find_ref_obj_id_regno(env, regs, nargs, true);
>> +       if (ref_regno < 0)
>> +               return ref_regno;
>> +       else if (ref_regno > 0)
>> +               meta.ref_obj_id = regs[ref_regno].ref_obj_id;
> 
> nit: I think it's easier to read if this ref_regno logic gets moved
> below the release_regno logic, so that all the release_regno logic is
> together
> 

Sorry for resurrecting this old thread. I sent a v4 which
applies feedback from this message, except for this nit and one
other suggestion (addressed below).

I agree with this readability nit, but did not apply the
suggestion as this weird order is intentional in order to match
the pre-refactoring order of error checks as much as possible.

>> +
>> +       if (release_regno > 0) {
>> +               if (!regs[release_regno].ref_obj_id &&
>> +                   !register_is_null(&regs[release_regno]) &&
>> +                   !arg_type_is_dynptr(fn->arg_type[release_regno - BPF_REG_1])) {
>> +                       verbose(env, "R%d must be referenced when passed to release function\n",
>> +                               release_regno);
>> +                       return -EINVAL;
>> +               }
>> +
>> +               meta.release_regno = release_regno;
>> +       }
>> +
>>         /* check args */
>> -       for (i = 0; i < MAX_BPF_FUNC_REG_ARGS; i++) {
>> +       for (i = 0; i < nargs; i++) {
>>                 err = check_func_arg(env, i, &meta, fn);
>>                 if (err)
>>                         return err;
>> @@ -8204,8 +8295,6 @@ static int check_helper_call(struct bpf_verifier_env *env, struct bpf_insn *insn
>>                         return err;
>>         }
>>
>> -       regs = cur_regs(env);
>> -
>>         /* This can only be set for PTR_TO_STACK, as CONST_PTR_TO_DYNPTR cannot
>>          * be reinitialized by any dynptr helper. Hence, mark_stack_slots_dynptr
>>          * is safe to do directly.
>> @@ -9442,10 +9531,11 @@ static int process_kf_arg_ptr_to_rbtree_node(struct bpf_verifier_env *env,
>>  static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_arg_meta *meta)
>>  {
>>         const char *func_name = meta->func_name, *ref_tname;
>> +       struct bpf_reg_state *regs = cur_regs(env);
>>         const struct btf *btf = meta->btf;
>>         const struct btf_param *args;
>> +       int ret, ref_regno;
>>         u32 i, nargs;
>> -       int ret;
>>
>>         args = (const struct btf_param *)(meta->func_proto + 1);
>>         nargs = btf_type_vlen(meta->func_proto);
>> @@ -9455,17 +9545,31 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
>>                 return -EINVAL;
>>         }
>>
>> +       ref_regno = args_find_ref_obj_id_regno(env, cur_regs(env), nargs, false);
> 
> nit: I think we can just pass in "regs" as the 2nd arg
> 
>> +       if (ref_regno < 0) {
>> +               return ref_regno;
>> +       } else if (!ref_regno && is_kfunc_release(meta)) {
>> +               verbose(env, "release kernel function %s expects refcounted PTR_TO_BTF_ID\n",
>> +                       func_name);
>> +               return -EINVAL;
>> +       }
>> +
>> +       meta->ref_obj_id = regs[ref_regno].ref_obj_id;
>> +       if (is_kfunc_release(meta))
>> +               meta->release_regno = ref_regno;
>> +
> 
> I think we also need to check that if the kfunc is a release func then
> there can't be more than one arg reg with a set ref_obj_id (the
> earlier call to args_find_ref_obj_id_regno doesn't catch this since we
> pass in false for err_multi)
> 

This "if more than one arg reg w/ a set ref_obj_id, consider
last such arg reg to be ref_regno" logic is preserving existing
behavior.

But more generally, RELEASE is really a parameter-level thing: if the function
is RELEASEing some passed-in resource, that parameter should be tagged as such
(OBJ_RELEASE type flag in helper func proto does this more cleanly IMO), then
there's no need to rely on heuristics to guess what's being released. Might
as well preserve the pre-refacotring heuristic here and move to parameter-level
flags for kfuncs eventually.

>>         /* Check that BTF function arguments match actual types that the
>>          * verifier sees.
>>          */
>>         for (i = 0; i < nargs; i++) {
>> -               struct bpf_reg_state *regs = cur_regs(env), *reg = &regs[i + 1];
>>                 const struct btf_type *t, *ref_t, *resolve_ret;
>>                 enum bpf_arg_type arg_type = ARG_DONTCARE;
>>                 u32 regno = i + 1, ref_id, type_size;
>>                 bool is_ret_buf_sz = false;
>> +               struct bpf_reg_state *reg;
>>                 int kf_arg_type;
>>
>> +               reg = &regs[regno];
>>                 t = btf_type_skip_modifiers(btf, args[i].type, NULL);
>>
>>                 if (is_kfunc_arg_ignore(btf, &args[i]))
>> @@ -9528,18 +9632,6 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
>>                         return -EACCES;
>>                 }
>>
>> -               if (reg->ref_obj_id) {
>> -                       if (is_kfunc_release(meta) && meta->ref_obj_id) {
>> -                               verbose(env, "verifier internal error: more than one arg with ref_obj_id R%d %u %u\n",
>> -                                       regno, reg->ref_obj_id,
>> -                                       meta->ref_obj_id);
>> -                               return -EFAULT;
>> -                       }
>> -                       meta->ref_obj_id = reg->ref_obj_id;
>> -                       if (is_kfunc_release(meta))
>> -                               meta->release_regno = regno;
>> -               }
>> -
>>                 ref_t = btf_type_skip_modifiers(btf, t->type, &ref_id);
>>                 ref_tname = btf_name_by_offset(btf, ref_t->name_off);
>>
>> @@ -9585,7 +9677,7 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
>>                         return -EFAULT;
>>                 }
>>
>> -               if (is_kfunc_release(meta) && reg->ref_obj_id)
>> +               if (is_kfunc_release(meta) && regno == meta->release_regno)
> 
> I don't think we need the "is_kfunc_release(meta)" check here since
> meta->release_regno is set to a regno only when is_kfunc_release(meta)
> is true
> 
>>                         arg_type |= OBJ_RELEASE;
>>                 ret = check_func_arg_reg_off(env, reg, regno, arg_type);
>>                 if (ret < 0)
>> @@ -9747,12 +9839,6 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
>>                 }
>>         }
>>
>> -       if (is_kfunc_release(meta) && !meta->release_regno) {
>> -               verbose(env, "release kernel function %s expects refcounted PTR_TO_BTF_ID\n",
>> -                       func_name);
>> -               return -EINVAL;
>> -       }
>> -
>>         return 0;
>>  }
>>
>> --
>> 2.30.2
>>



[Index of Archives]     [Linux Samsung SoC]     [Linux Rockchip SoC]     [Linux Actions SoC]     [Linux for Synopsys ARC Processors]     [Linux NFS]     [Linux NILFS]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]


  Powered by Linux