Re: [PATCH bpf-next 3/7] bpf: Improve handling of pattern '<const> <cond_op> <non_const>' in verifier

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

 



On Thu, Mar 30, 2023 at 3:55 PM Dave Marchevsky <davemarchevsky@xxxxxxxx> wrote:
>
> On 3/30/23 1:56 AM, Yonghong Song wrote:
> > Currently, the verifier does not handle '<const> <cond_op> <non_const>' well.
> > For example,
> >   ...
> >   10: (79) r1 = *(u64 *)(r10 -16)       ; R1_w=scalar() R10=fp0
> >   11: (b7) r2 = 0                       ; R2_w=0
> >   12: (2d) if r2 > r1 goto pc+2
> >   13: (b7) r0 = 0
> >   14: (95) exit
> >   15: (65) if r1 s> 0x1 goto pc+3
> >   16: (0f) r0 += r1
> >   ...
> > At insn 12, verifier decides both true and false branch are possible, but
> > actually only false branch is possible.
> >
> > Currently, the verifier already supports patterns '<non_const> <cond_op> <const>.
> > Add support for patterns '<const> <cond_op> <non_const>' in a similar way.
> >
> > Also fix selftest 'verifier_bounds_mix_sign_unsign/bounds checks mixing signed and unsigned, variant 10'
> > due to this change.
> >
> > Signed-off-by: Yonghong Song <yhs@xxxxxx>
> > ---
> >  kernel/bpf/verifier.c                                | 12 ++++++++++++
> >  .../bpf/progs/verifier_bounds_mix_sign_unsign.c      |  2 +-
> >  2 files changed, 13 insertions(+), 1 deletion(-)
> >
> > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> > index 90bb6d25bc9c..d070943a8ba1 100644
> > --- a/kernel/bpf/verifier.c
> > +++ b/kernel/bpf/verifier.c
> > @@ -13302,6 +13302,18 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env,
> >                                      src_reg->var_off.value,
> >                                      opcode,
> >                                      is_jmp32);
> > +     } else if (dst_reg->type == SCALAR_VALUE &&
> > +                is_jmp32 && tnum_is_const(tnum_subreg(dst_reg->var_off))) {
> > +             pred = is_branch_taken(src_reg,
> > +                                    tnum_subreg(dst_reg->var_off).value,
> > +                                    flip_opcode(opcode),
> > +                                    is_jmp32);
> > +     } else if (dst_reg->type == SCALAR_VALUE &&
> > +                !is_jmp32 && tnum_is_const(dst_reg->var_off)) {
> > +             pred = is_branch_taken(src_reg,
> > +                                    dst_reg->var_off.value,
> > +                                    flip_opcode(opcode),
> > +                                    is_jmp32);
> >       } else if (reg_is_pkt_pointer_any(dst_reg) &&
> >                  reg_is_pkt_pointer_any(src_reg) &&
> >                  !is_jmp32) {
>
> Looking at the two SCALAR_VALUE 'else if's above these added lines, these
> additions make sense. Having four separate 'else if' checks for essentially
> similar logic makes this hard to read, though, maybe it's an opportunity to
> refactor a bit.
>
> While trying to make sense of the logic here I attempted to simplify with
> a helper:
>
> @@ -13234,6 +13234,21 @@ static void find_equal_scalars(struct bpf_verifier_state *vstate,
>         }));
>  }
>
> +static int maybe_const_operand_branch(struct tnum maybe_const_op,
> +                                     struct bpf_reg_state *other_op_reg,
> +                                     u8 opcode, bool is_jmp32)
> +{
> +       struct tnum jmp_tnum = is_jmp32 ? tnum_subreg(maybe_const_op) :
> +                                         maybe_const_op;
> +       if (!tnum_is_const(jmp_tnum))
> +               return -1;
> +
> +       return is_branch_taken(other_op_reg,
> +                              jmp_tnum.value,
> +                              opcode,
> +                              is_jmp32);
> +}
> +
>  static int check_cond_jmp_op(struct bpf_verifier_env *env,
>                              struct bpf_insn *insn, int *insn_idx)
>  {
> @@ -13287,18 +13302,12 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env,
>
>         if (BPF_SRC(insn->code) == BPF_K) {
>                 pred = is_branch_taken(dst_reg, insn->imm, opcode, is_jmp32);
> -       } else if (src_reg->type == SCALAR_VALUE &&
> -                  is_jmp32 && tnum_is_const(tnum_subreg(src_reg->var_off))) {
> -               pred = is_branch_taken(dst_reg,
> -                                      tnum_subreg(src_reg->var_off).value,
> -                                      opcode,
> -                                      is_jmp32);
> -       } else if (src_reg->type == SCALAR_VALUE &&
> -                  !is_jmp32 && tnum_is_const(src_reg->var_off)) {
> -               pred = is_branch_taken(dst_reg,
> -                                      src_reg->var_off.value,
> -                                      opcode,
> -                                      is_jmp32);
> +       } else if (src_reg->type == SCALAR_VALUE) {
> +               pred = maybe_const_operand_branch(src_reg->var_off, dst_reg,
> +                                                 opcode, is_jmp32);
> +       } else if (dst_reg->type == SCALAR_VALUE) {
> +               pred = maybe_const_operand_branch(dst_reg->var_off, src_reg,
> +                                                 flip_opcode(opcode), is_jmp32);
>         } else if (reg_is_pkt_pointer_any(dst_reg) &&
>                    reg_is_pkt_pointer_any(src_reg) &&
>                    !is_jmp32) {
>
>
> I think the resultant logic is the same as your patch, but it's easier to
> understand, for me at least. Note that I didn't test the above.

should we push it half a step further and have

if (src_reg->type == SCALAR_VALUE || dst_reg->type == SCALAR_VALUE)
  pred = is_branch_taken_regs(src_reg, dst_reg, opcode, is_jmp32)

seems even clearer like that. All the tnum subreg, const vs non-const,
and dst/src flip can be handled internally in one nicely isolated
place.




[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