On Thu, May 28, 2020 at 09:50:43AM -0700, Yonghong Song wrote: > With the latest trunk llvm (llvm 11), I hit a verifier issue for > test_prog subtest test_verif_scale1. > > The following simplified example illustrate the issue: > w9 = 0 /* R9_w=inv0 */ > r8 = *(u32 *)(r1 + 80) /* __sk_buff->data_end */ > r7 = *(u32 *)(r1 + 76) /* __sk_buff->data */ > ...... > w2 = w9 /* R2_w=inv0 */ > r6 = r7 /* R6_w=pkt(id=0,off=0,r=0,imm=0) */ > r6 += r2 /* R6_w=inv(id=0) */ > r3 = r6 /* R3_w=inv(id=0) */ > r3 += 14 /* R3_w=inv(id=0) */ > if r3 > r8 goto end > r5 = *(u32 *)(r6 + 0) /* R6_w=inv(id=0) */ > <== error here: R6 invalid mem access 'inv' > ... > end: > > In real test_verif_scale1 code, "w9 = 0" and "w2 = w9" are in > different basic blocks. > > In the above, after "r6 += r2", r6 becomes a scalar, which eventually > caused the memory access error. The correct register state should be > a pkt pointer. > > The inprecise register state starts at "w2 = w9". > The 32bit register w9 is 0, in __reg_assign_32_into_64(), > the 64bit reg->smax_value is assigned to be U32_MAX. > The 64bit reg->smin_value is 0 and the 64bit register > itself remains constant based on reg->var_off. > > In adjust_ptr_min_max_vals(), the verifier checks for a known constant, > smin_val must be equal to smax_val. Since they are not equal, > the verifier decides r6 is a unknown scalar, which caused later failure. > > The llvm10 does not have this issue as it generates different code: > w9 = 0 /* R9_w=inv0 */ > r8 = *(u32 *)(r1 + 80) /* __sk_buff->data_end */ > r7 = *(u32 *)(r1 + 76) /* __sk_buff->data */ > ...... > r6 = r7 /* R6_w=pkt(id=0,off=0,r=0,imm=0) */ > r6 += r9 /* R6_w=pkt(id=0,off=0,r=0,imm=0) */ > r3 = r6 /* R3_w=pkt(id=0,off=0,r=0,imm=0) */ > r3 += 14 /* R3_w=pkt(id=0,off=14,r=0,imm=0) */ > if r3 > r8 goto end > ... > > To fix the issue, if 32bit register is a const 0, > then just assign max vaue 0 to 64bit register smax_value as well. > > Fixes: 3f50f132d840 ("bpf: Verifier, do explicit ALU32 bounds tracking") > Cc: John Fastabend <john.fastabend@xxxxxxxxx> > Signed-off-by: Yonghong Song <yhs@xxxxxx> > --- > kernel/bpf/verifier.c | 3 +++ > 1 file changed, 3 insertions(+) > > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > index 8d7ee40e2748..5123ce54695f 100644 > --- a/kernel/bpf/verifier.c > +++ b/kernel/bpf/verifier.c > @@ -1174,6 +1174,9 @@ static void __reg_assign_32_into_64(struct bpf_reg_state *reg) > reg->smin_value = 0; > if (reg->s32_max_value > 0) > reg->smax_value = reg->s32_max_value; > + else if (reg->s32_max_value == 0 && reg->s32_min_value == 0 && > + tnum_is_const(reg->var_off)) > + reg->smax_value = 0; /* const 0 */ > else > reg->smax_value = U32_MAX; wouldn't this be a more general fix ? diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 01c7d3634151..83450d5d24ab 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -1217,11 +1217,11 @@ static void __reg_assign_32_into_64(struct bpf_reg_state *reg) * but must be positive otherwise set to worse case bounds * and refine later from tnum. */ - if (reg->s32_min_value > 0) + if (reg->s32_min_value >= 0) reg->smin_value = reg->s32_min_value; else reg->smin_value = 0; - if (reg->s32_max_value > 0) + if (reg->s32_max_value >= 0) reg->smax_value = reg->s32_max_value; else reg->smax_value = U32_MAX;