Re: [PATCH bpf-next v3 1/3] bpf: Mark raw_tp arguments with PTR_MAYBE_NULL

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

 



On Mon, Nov 4, 2024 at 9:20 AM Kumar Kartikeya Dwivedi <memxor@xxxxxxxxx> wrote:
>
> Arguments to a raw tracepoint are tagged as trusted, which carries the
> semantics that the pointer will be non-NULL.  However, in certain cases,
> a raw tracepoint argument may end up being NULL. More context about this
> issue is available in [0].
>
> Thus, there is a discrepancy between the reality, that raw_tp arguments
> can actually be NULL, and the verifier's knowledge, that they are never
> NULL, causing explicit NULL checks to be deleted, and accesses to such
> pointers potentially crashing the kernel.
>
> To fix this, mark raw_tp arguments as PTR_MAYBE_NULL, and then special
> case the dereference and pointer arithmetic to permit it, and allow
> passing them into helpers/kfuncs; these exceptions are made for raw_tp
> programs only. Ensure that we don't do this when ref_obj_id > 0, as in
> that case this is an acquired object and doesn't need such adjustment.
>
> The reason we do mask_raw_tp_trusted_reg logic is because other will
> recheck in places whether the register is a trusted_reg, and then
> consider our register as untrusted when detecting the presence of the
> PTR_MAYBE_NULL flag.
>
> To allow safe dereference, we enable PROBE_MEM marking when we see loads
> into trusted pointers with PTR_MAYBE_NULL.
>
> While trusted raw_tp arguments can also be passed into helpers or kfuncs
> where such broken assumption may cause issues, a future patch set will
> tackle their case separately, as PTR_TO_BTF_ID (without PTR_TRUSTED) can
> already be passed into helpers and causes similar problems. Thus, they
> are left alone for now.
>
> It is possible that these checks also permit passing non-raw_tp args
> that are trusted PTR_TO_BTF_ID with null marking. In such a case,
> allowing dereference when pointer is NULL expands allowed behavior, so
> won't regress existing programs, and the case of passing these into
> helpers is the same as above and will be dealt with later.
>
> Also update the failure case in tp_btf_nullable selftest to capture the
> new behavior, as the verifier will no longer cause an error when
> directly dereference a raw tracepoint argument marked as __nullable.
>
>   [0]: https://lore.kernel.org/bpf/ZrCZS6nisraEqehw@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
>
> Reviewed-by: Jiri Olsa <jolsa@xxxxxxxxxx>
> Reported-by: Juri Lelli <juri.lelli@xxxxxxxxxx>
> Tested-by: Juri Lelli <juri.lelli@xxxxxxxxxx>
> Fixes: 3f00c5239344 ("bpf: Allow trusted pointers to be passed to KF_TRUSTED_ARGS kfuncs")
> Signed-off-by: Kumar Kartikeya Dwivedi <memxor@xxxxxxxxx>
> ---
>  include/linux/bpf.h                           |  6 ++
>  kernel/bpf/btf.c                              |  5 +-
>  kernel/bpf/verifier.c                         | 79 +++++++++++++++++--
>  .../bpf/progs/test_tp_btf_nullable.c          |  6 +-
>  4 files changed, 87 insertions(+), 9 deletions(-)
>

[...]

> @@ -12065,12 +12109,15 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
>                         return -EINVAL;
>                 }
>
> +               mask = mask_raw_tp_reg(env, reg);
>                 if ((is_kfunc_trusted_args(meta) || is_kfunc_rcu(meta)) &&
>                     (register_is_null(reg) || type_may_be_null(reg->type)) &&
>                         !is_kfunc_arg_nullable(meta->btf, &args[i])) {
>                         verbose(env, "Possibly NULL pointer passed to trusted arg%d\n", i);
> +                       unmask_raw_tp_reg(reg, mask);

Kumar,

Do we really need this unmask? We are already erroring out, restoring
reg->type is probably not very important at this point?

>                         return -EACCES;
>                 }
> +               unmask_raw_tp_reg(reg, mask);
>
>                 if (reg->ref_obj_id) {
>                         if (is_kfunc_release(meta) && meta->ref_obj_id) {
> @@ -12128,16 +12175,24 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
>                         if (!is_kfunc_trusted_args(meta) && !is_kfunc_rcu(meta))
>                                 break;
>
> +                       /* Allow passing maybe NULL raw_tp arguments to
> +                        * kfuncs for compatibility. Don't apply this to
> +                        * arguments with ref_obj_id > 0.
> +                        */
> +                       mask = mask_raw_tp_reg(env, reg);
>                         if (!is_trusted_reg(reg)) {
>                                 if (!is_kfunc_rcu(meta)) {
>                                         verbose(env, "R%d must be referenced or trusted\n", regno);
> +                                       unmask_raw_tp_reg(reg, mask);

same as above, do we care about unmasking in this situation? and the
one immediately below?

>                                         return -EINVAL;
>                                 }
>                                 if (!is_rcu_reg(reg)) {
>                                         verbose(env, "R%d must be a rcu pointer\n", regno);
> +                                       unmask_raw_tp_reg(reg, mask);
>                                         return -EINVAL;
>                                 }
>                         }
> +                       unmask_raw_tp_reg(reg, mask);
>                         fallthrough;
>                 case KF_ARG_PTR_TO_CTX:
>                 case KF_ARG_PTR_TO_DYNPTR:
> @@ -12160,7 +12215,9 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
>
>                 if (is_kfunc_release(meta) && reg->ref_obj_id)
>                         arg_type |= OBJ_RELEASE;
> +               mask = mask_raw_tp_reg(env, reg);
>                 ret = check_func_arg_reg_off(env, reg, regno, arg_type);
> +               unmask_raw_tp_reg(reg, mask);
>                 if (ret < 0)
>                         return ret;
>
> @@ -12337,6 +12394,7 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
>                         ref_tname = btf_name_by_offset(btf, ref_t->name_off);
>                         fallthrough;
>                 case KF_ARG_PTR_TO_BTF_ID:
> +                       mask = mask_raw_tp_reg(env, reg);
>                         /* Only base_type is checked, further checks are done here */
>                         if ((base_type(reg->type) != PTR_TO_BTF_ID ||
>                              (bpf_type_has_unsafe_modifiers(reg->type) && !is_rcu_reg(reg))) &&
> @@ -12345,9 +12403,11 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
>                                 verbose(env, "expected %s or socket\n",
>                                         reg_type_str(env, base_type(reg->type) |
>                                                           (type_flag(reg->type) & BPF_REG_TRUSTED_MODIFIERS)));
> +                               unmask_raw_tp_reg(reg, mask);

ditto

>                                 return -EINVAL;
>                         }
>                         ret = process_kf_arg_ptr_to_btf_id(env, reg, ref_t, ref_tname, ref_id, meta, i);
> +                       unmask_raw_tp_reg(reg, mask);
>                         if (ret < 0)
>                                 return ret;
>                         break;
> @@ -13320,7 +13380,7 @@ static int sanitize_check_bounds(struct bpf_verifier_env *env,
>   */
>  static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
>                                    struct bpf_insn *insn,
> -                                  const struct bpf_reg_state *ptr_reg,
> +                                  struct bpf_reg_state *ptr_reg,
>                                    const struct bpf_reg_state *off_reg)
>  {
>         struct bpf_verifier_state *vstate = env->cur_state;
> @@ -13334,6 +13394,7 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
>         struct bpf_sanitize_info info = {};
>         u8 opcode = BPF_OP(insn->code);
>         u32 dst = insn->dst_reg;
> +       bool mask;
>         int ret;
>
>         dst_reg = &regs[dst];
> @@ -13360,11 +13421,14 @@ static int adjust_ptr_min_max_vals(struct bpf_verifier_env *env,
>                 return -EACCES;
>         }
>
> +       mask = mask_raw_tp_reg(env, ptr_reg);
>         if (ptr_reg->type & PTR_MAYBE_NULL) {
>                 verbose(env, "R%d pointer arithmetic on %s prohibited, null-check it first\n",
>                         dst, reg_type_str(env, ptr_reg->type));
> +               unmask_raw_tp_reg(ptr_reg, mask);

ditto

>                 return -EACCES;
>         }
> +       unmask_raw_tp_reg(ptr_reg, mask);
>
>         switch (base_type(ptr_reg->type)) {
>         case PTR_TO_CTX:
> @@ -19866,6 +19930,7 @@ static int convert_ctx_accesses(struct bpf_verifier_env *env)
>                  * for this case.
>                  */
>                 case PTR_TO_BTF_ID | MEM_ALLOC | PTR_UNTRUSTED:
> +               case PTR_TO_BTF_ID | PTR_TRUSTED | PTR_MAYBE_NULL:
>                         if (type == BPF_READ) {
>                                 if (BPF_MODE(insn->code) == BPF_MEM)
>                                         insn->code = BPF_LDX | BPF_PROBE_MEM |
> diff --git a/tools/testing/selftests/bpf/progs/test_tp_btf_nullable.c b/tools/testing/selftests/bpf/progs/test_tp_btf_nullable.c
> index bba3e37f749b..5aaf2b065f86 100644
> --- a/tools/testing/selftests/bpf/progs/test_tp_btf_nullable.c
> +++ b/tools/testing/selftests/bpf/progs/test_tp_btf_nullable.c
> @@ -7,7 +7,11 @@
>  #include "bpf_misc.h"
>
>  SEC("tp_btf/bpf_testmod_test_nullable_bare")
> -__failure __msg("R1 invalid mem access 'trusted_ptr_or_null_'")
> +/* This used to be a failure test, but raw_tp nullable arguments can now
> + * directly be dereferenced, whether they have nullable annotation or not,
> + * and don't need to be explicitly checked.
> + */
> +__success
>  int BPF_PROG(handle_tp_btf_nullable_bare1, struct bpf_testmod_test_read_ctx *nullable_ctx)
>  {
>         return nullable_ctx->len;
> --
> 2.43.5
>





[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