RE: [PATCH bpf-next 1/2] bpf: improve verifier handling for 32bit signed compare operations

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

 



Yonghong Song wrote:
> Commit b7a0d65d80a0 ("bpf, testing: Workaround a verifier failure
> for test_progs") worked around a verifier failure where the
> register is copied to another later refined register, but the
> original register is used after refinement. Another similar example is
>   https://lore.kernel.org/netdev/871019a0-71f8-c26d-0ae8-c7fd8c8867fc@xxxxxx/
> 
> LLVM commit https://reviews.llvm.org/D72787 added a phase

FWIW I was going to try and see if we could refine the bounds by
walking parents chain. But that is an experiment tbd.

> to adjust optimization such that the original register is
> directly refined and used later. Another issue exposed by
> the llvm is verifier cannot handle the following code:
>   call bpf_strtoul
>   if w0 s< 1 then ...
>   if w0 s> 7 then ...
>   ... use w0 ...
> 
> Unfortunately, the verifier is not able to handle the above
> code well and will reject it.
>   call bpf_strtoul
>     R0_w=inv(id=0) R8=invP0
>   if w0 s< 0x1 goto pc-22
>     R0_w=inv(id=0) R8=invP0
>   if w0 s> 0x7 goto pc-23
>     R0=inv(id=0) R8=invP0
>   w0 += w8
>     R0_w=inv(id=0,umax_value=4294967295,var_off=(0x0; 0xffffffff)) R8=invP0
> 
> After "w0 += w8", we got a very conservative R0 value, which
> later on caused verifier rejection.
> 
> This patch added two register states, s32_min_value and s32_max_value,
> to bpf_reg_state. These two states capture the signed 32bit
> min/max values refined due to 32bit signed sle/slt/sge/sgt comparisons.
>   1. whenever refined s32_min_value, s32_max_value is captured, reg->var_off
>      will be refined if possible.
>   2. For any ALU32 operation where the dst_reg will have upper 32bit cleared,
>      if s32_min_value >= 0 and s32_max_value has been narrowed due to previous
>      signed compare operation, the dst_reg as an input can ignore upper 32bit values,
>      this may produce better output dst_reg value range.

Can you comment a bit more on the s32_min_value < 0 case? Regardless of the
s32_{min|max}_value the result should be zero extended and smin_value=0. This
is enforced by verifier_zext(), an aside but I think we should just remove
verifier_zext its not very useful if all jits have to comply imo.

If smin_value=0 && s32_max_value>=0 then we should be safe to propagate s32_max_value
into smax_Value as well.

>   3. s32_min_value and s32_max_value is reset if the corresponding register
>      is redefined.
> 
> The following shows the new register states for the above example.
>   call bpf_strtoul
>     R0_w=inv(id=0) R8=invP0
>   if w0 s< 0x1 goto pc-22
>     R0_w=inv(id=0,smax_value=9223372034707292159,umax_value=18446744071562067967,
>              s32_min_value=1,var_off=(0x0; 0xffffffff7fffffff))
>     R8=invP0
>   if w0 s> 0x7 goto pc-23
>     R0=inv(id=0,smax_value=9223372032559808519,umax_value=18446744069414584327,
>            s32_min_value=1,s32_max_value=7,var_off=(0x0; 0xffffffff00000007))
>     R8=invP0
>   w0 += w8
>     R0_w=inv(id=0,umax_value=7,var_off=(0x0; 0x7)) R8=invP0

And we should also have smin_value=0?

> 
> With the above LLVM patch and this commit, the original
> workaround in Commit b7a0d65d80a0 is not needed any more.
> 
> Signed-off-by: Yonghong Song <yhs@xxxxxx>
> ---
>  include/linux/bpf_verifier.h |  2 +
>  kernel/bpf/verifier.c        | 73 +++++++++++++++++++++++++++++++-----
>  2 files changed, 65 insertions(+), 10 deletions(-)
> 
> diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
> index 5406e6e96585..d5694308466d 100644
> --- a/include/linux/bpf_verifier.h
> +++ b/include/linux/bpf_verifier.h
> @@ -123,6 +123,8 @@ struct bpf_reg_state {
>  	s64 smax_value; /* maximum possible (s64)value */
>  	u64 umin_value; /* minimum possible (u64)value */
>  	u64 umax_value; /* maximum possible (u64)value */
> +	s32 s32_min_value; /* minimum possible (s32)value */
> +	s32 s32_max_value; /* maximum possible (s32)value */
>  	/* parentage chain for liveness checking */
>  	struct bpf_reg_state *parent;
>  	/* Inside the callee two registers can be both PTR_TO_STACK like
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 1cc945daa9c8..c5d6835c38db 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -543,6 +543,14 @@ static void print_verifier_state(struct bpf_verifier_env *env,
>  				if (reg->umax_value != U64_MAX)
>  					verbose(env, ",umax_value=%llu",
>  						(unsigned long long)reg->umax_value);
> +				if (reg->s32_min_value != reg->umin_value &&
> +				    reg->s32_min_value != S32_MIN)
> +					verbose(env, ",s32_min_value=%d",
> +						(int)reg->s32_min_value);
> +				if (reg->s32_max_value != reg->umax_value &&
> +				    reg->s32_max_value != S32_MAX)
> +					verbose(env, ",s32_max_value=%d",
> +						(int)reg->s32_max_value);
>  				if (!tnum_is_unknown(reg->var_off)) {
>  					char tn_buf[48];
>  
> @@ -923,6 +931,10 @@ static void __mark_reg_known(struct bpf_reg_state *reg, u64 imm)
>  	reg->smax_value = (s64)imm;
>  	reg->umin_value = imm;
>  	reg->umax_value = imm;
> +
> +	/* no need to be precise, just reset s32_{min,max}_value */
> +	reg->s32_min_value = S32_MIN;
> +	reg->s32_max_value = S32_MAX;

If its known it would make more sense to me to set the min/max value vs this
shortcut. Otherwise we wont have bounds to compare against on a JMP32.

>  }

Still thinking through the rest, but figured it would be worth kicking the
couple comments above out.. I'm trying to understand if the coerce_reg_size()
can be made a bit cleaner.

Thanks,
John



[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