Re: [PATCH bpf-next 1/2] bpf: Warn with new bpf_unreachable() kfunc maybe due to uninitialized var

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

 



On Sun, May 11, 2025 at 11:28 AM Yonghong Song <yonghong.song@xxxxxxxxx> wrote:
>
> Marc Suñé (Isovalent, part of Cisco) reported an issue where an
> uninitialized variable caused generating bpf prog binary code not
> working as expected. The reproducer is in [1] where the flags
> “-Wall -Werror” are enabled, but there is no warning and compiler
> may take advantage of uninit variable to do aggressive optimization.
>
> In llvm internals, uninitialized variable usage may generate
> 'unreachable' IR insn and these 'unreachable' IR insns may indicate
> uninit var impact on code optimization. With clang21 patch [2],
> those 'unreachable' IR insn are converted to func bpf_unreachable().
>
> In kernel, a new kfunc bpf_unreachable() is added. If this kfunc
> (generated by [2]) is the last insn in the main prog or a subprog,
> the verifier will suggest the verification failure may be due to
> uninitialized var, so user can check their source code to find the
> root cause.
>
> Without this patch, the verifier will output
>   last insn is not an exit or jmp
> and user will not know what is the potential root cause and
> it will take more time to debug this verification failure.
>
>   [1] https://github.com/msune/clang_bpf/blob/main/Makefile#L3
>   [2] https://github.com/llvm/llvm-project/pull/131731
>
> Signed-off-by: Yonghong Song <yonghong.song@xxxxxxxxx>
> ---
>  kernel/bpf/helpers.c  |  5 +++++
>  kernel/bpf/verifier.c | 17 ++++++++++++++++-
>  2 files changed, 21 insertions(+), 1 deletion(-)
>
> In order to compile kernel successfully with the above [2], the following
> change is needed due to clang21 changes:
>
>   --- a/Makefile
>   +++ b/Makefile
>   @@ -852,7 +852,7 @@ endif
>    endif # may-sync-config
>    endif # need-config
>
>   -KBUILD_CFLAGS  += -fno-delete-null-pointer-checks
>   +KBUILD_CFLAGS  += -fno-delete-null-pointer-checks -Wno-default-const-init-field-unsafe
>
>   --- a/scripts/Makefile.extrawarn
>   +++ b/scripts/Makefile.extrawarn
>   @@ -19,6 +19,7 @@ KBUILD_CFLAGS += $(call cc-disable-warning, frame-address)
>    KBUILD_CFLAGS += $(call cc-disable-warning, address-of-packed-member)
>    KBUILD_CFLAGS += -Wmissing-declarations
>    KBUILD_CFLAGS += -Wmissing-prototypes
>   +KBUILD_CFLAGS += -Wno-default-const-init-var-unsafe
>
> diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
> index fed53da75025..6048d7e19d4c 100644
> --- a/kernel/bpf/helpers.c
> +++ b/kernel/bpf/helpers.c
> @@ -3283,6 +3283,10 @@ __bpf_kfunc void bpf_local_irq_restore(unsigned long *flags__irq_flag)
>         local_irq_restore(*flags__irq_flag);
>  }
>
> +__bpf_kfunc void bpf_unreachable(void)
> +{
> +}

Does it have to be an actual function with the body?
Can it be a kfunc that doesn't consume any .text ?

> +
>  __bpf_kfunc_end_defs();
>
>  BTF_KFUNCS_START(generic_btf_ids)
> @@ -3388,6 +3392,7 @@ BTF_ID_FLAGS(func, bpf_iter_kmem_cache_next, KF_ITER_NEXT | KF_RET_NULL | KF_SLE
>  BTF_ID_FLAGS(func, bpf_iter_kmem_cache_destroy, KF_ITER_DESTROY | KF_SLEEPABLE)
>  BTF_ID_FLAGS(func, bpf_local_irq_save)
>  BTF_ID_FLAGS(func, bpf_local_irq_restore)
> +BTF_ID_FLAGS(func, bpf_unreachable)
>  BTF_KFUNCS_END(common_btf_ids)
>
>  static const struct btf_kfunc_id_set common_kfunc_set = {
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 28f5a7899bd6..d26aec0a90d0 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -206,6 +206,7 @@ static int ref_set_non_owning(struct bpf_verifier_env *env,
>  static void specialize_kfunc(struct bpf_verifier_env *env,
>                              u32 func_id, u16 offset, unsigned long *addr);
>  static bool is_trusted_reg(const struct bpf_reg_state *reg);
> +static void verbose_insn(struct bpf_verifier_env *env, struct bpf_insn *insn);
>
>  static bool bpf_map_ptr_poisoned(const struct bpf_insn_aux_data *aux)
>  {
> @@ -3398,7 +3399,10 @@ static int check_subprogs(struct bpf_verifier_env *env)
>         int i, subprog_start, subprog_end, off, cur_subprog = 0;
>         struct bpf_subprog_info *subprog = env->subprog_info;
>         struct bpf_insn *insn = env->prog->insnsi;
> +       bool is_bpf_unreachable = false;
>         int insn_cnt = env->prog->len;
> +       const struct btf_type *t;
> +       const char *tname;
>
>         /* now check that all jumps are within the same subprog */
>         subprog_start = subprog[cur_subprog].start;
> @@ -3433,7 +3437,18 @@ static int check_subprogs(struct bpf_verifier_env *env)
>                         if (code != (BPF_JMP | BPF_EXIT) &&
>                             code != (BPF_JMP32 | BPF_JA) &&
>                             code != (BPF_JMP | BPF_JA)) {
> -                               verbose(env, "last insn is not an exit or jmp\n");
> +                               verbose_insn(env, &insn[i]);
> +                               if (btf_vmlinux && insn[i].code == (BPF_CALL | BPF_JMP) &&
> +                                   insn[i].src_reg == BPF_PSEUDO_KFUNC_CALL) {
> +                                       t = btf_type_by_id(btf_vmlinux, insn[i].imm);
> +                                       tname = btf_name_by_offset(btf_vmlinux, t->name_off);
> +                                       if (strcmp(tname, "bpf_unreachable") == 0)

the check by name is not pretty.

> +                                               is_bpf_unreachable = true;
> +                               }
> +                               if (is_bpf_unreachable)
> +                                       verbose(env, "last insn is bpf_unreachable, due to uninitialized var?\n");
> +                               else
> +                                       verbose(env, "last insn is not an exit or jmp\n");

This is too specific imo.
add_subprog_and_kfunc() -> add_kfunc_call()
should probably handle it instead,
and print that error.
It doesn't matter that call bpf_unreachable is the last insn
of a program or subprogram.
I suspect llvm can emit it anywhere.





[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