2022-11-07 05:03:12

by Hao Sun

[permalink] [raw]
Subject: BUG in BPF verifier, 10 insns costs 2 mins+ to load

Hi,

I've just written a BPF verifier fuzzer, targeting logic bugs in the
BPF verifier.
The following is an abnormal case it generated. The case only contains 10
BPF instructions but costs more than 2 mins to load on :
HEAD commit: f0c4d9fc9cc9 Linux 6.1-rc4
git tree: upstream
kernel config: https://pastebin.com/raw/SBxaikiG
C reproducer: https://pastebin.com/raw/HsDXdraZ
verifier log: https://pastebin.com/raw/sNmSsVxs

Ideally, the verifier should exit quickly in this case, since R2=42
always holds.
The behaviour of the verifier does not make sense to me, seems it lost
the range information of R2.

Please point out if I missed anything, the C reproducer in the link
(https://pastebin.com/raw/HsDXdraZ)
essentially loads the following case into `test_verifier.c`:
{
"BVF verifier test",
.insns = {
BPF_MOV64_IMM(BPF_REG_1, 42),
BPF_MOV64_IMM(BPF_REG_2, 0),
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 1, 0, 1),
BPF_EXIT_INSN(),
BPF_ALU64_REG(BPF_ADD, BPF_REG_2, BPF_REG_1),
BPF_ALU32_IMM(BPF_DIV, BPF_REG_2, 1),
BPF_ALU64_IMM(BPF_SUB, BPF_REG_1, 108),
BPF_JMP32_REG(BPF_JGT, BPF_REG_1, BPF_REG_2, -3),
BPF_MOV64_REG(BPF_REG_0, BPF_REG_2),
BPF_EXIT_INSN(),
},
.prog_type = BPF_PROG_TYPE_XDP,
},

The verifier's log is more then 4M, but essentially is:
0: R1=ctx(off=0,imm=0) R10=fp0
0: (b7) r1 = 42 ; R1_w=P42
1: (b7) r2 = 0 ; R2_w=P0
2: (85) call pc+1
caller:
R10=fp0
callee:
frame1: R1_w=P42 R2_w=P0 R10=fp0
4: (57) r2 &= -52 ; frame1: R2_w=P0
5: (0f) r2 += r1 ; frame1: R1_w=P42 R2_w=P42
6: (34) w2 /= 1 ; frame1:
R2_w=Pscalar(umax=4294967295,var_off=(0x0; 0xffffffff))
7: (17) r1 -= 108 ; frame1: R1_w=P-66
8: (2e) if w1 > w2 goto pc-3 6: frame1: R1_w=P-66
R2_w=Pscalar(umax=4294967229,var_off=(0x0; 0xffffffff)) R10=fp0
6: (34) w2 /= 1 ; frame1:
R2_w=Pscalar(umax=4294967295,var_off=(0x0; 0xffffffff))
7: (17) r1 -= 108 ; frame1: R1_w=P-174
8: (2e) if w1 > w2 goto pc-3 6: frame1: R1_w=P-174
R2_w=Pscalar(umax=4294967121,var_off=(0x0; 0xffffffff)) R10=fp0
6: (34) w2 /= 1 ; frame1:
R2_w=Pscalar(umax=4294967295,var_off=(0x0; 0xffffffff))
7: (17) r1 -= 108 ; frame1: R1=P-282
8: (2e) if w1 > w2 goto pc-3 6: frame1: R1=P-282
R2=Pscalar(umax=4294967013,var_off=(0x0; 0xffffffff)) R10=fp0
...
6: (34) w2 /= 1 ; frame1:
R2_w=Pscalar(umax=4294967295,var_off=(0x0; 0xffffffff))
7: (17) r1 -= 108 ; frame1: R1_w=P-6342690
8: (2e) if w1 > w2 goto pc-3 6: frame1: R1_w=P-6342690
R2_w=Pscalar(umax=4288624605,var_off=(0x0; 0xffffffff)) R10=fp0
6: (34) w2 /= 1 ; frame1:
R2_w=Pscalar(umax=4294967295,var_off=(0x0; 0xffffffff))
7: (17) r1 -= 108 ; frame1: R1_w=P-6342798
8: (2e) if w1 > w2 goto pc-3 ; frame1: R1_w=P-6342798
R2_w=Pscalar(umin=4288624498,umax=4294967295,var_off=(0xff800000;
0x7fffff),s32_min=-6342798,s32_max=-1)
9: (bf) r0 = r2 ; frame1:
R0_w=Pscalar(id=58730,umin=4288624498,umax=4294967295,var_off=(0xff800000;
0x7fffff),s32_min=-6342798,s32_max=-1)
R2_w=Pscalar(id=58730,umin=4288624498,umax=4294967295,var_off=(0xff800000;
0x7fffff),s32_min=-6342798,s32_max=-1)
10: (95) exit
returning from callee:
frame1: R0_w=Pscalar(id=58730,umin=4288624498,umax=4294967295,var_off=(0xff800000;
0x7fffff),s32_min=-6342798,s32_max=-1) R1_w=P-6342798
R2_w=Pscalar(id=58730,umin=4288624498,umax=4294967295,var_off=(0xff800000;
0x7fffff),s32_min=-6342798,s32_max=-1) R10=fp0
to caller at 3:
R0_w=Pscalar(id=58730,umin=4288624498,umax=429496


2022-11-07 21:15:32

by Alexei Starovoitov

[permalink] [raw]
Subject: Re: BUG in BPF verifier, 10 insns costs 2 mins+ to load

On Sun, Nov 6, 2022 at 8:26 PM Hao Sun <[email protected]> wrote:
>
> Hi,
>
> I've just written a BPF verifier fuzzer, targeting logic bugs in the
> BPF verifier.
> The following is an abnormal case it generated. The case only contains 10
> BPF instructions but costs more than 2 mins to load on :

with full verbose verifier logging, right?
That is expected for any prog that is going to hit the 1M insn limit.

> HEAD commit: f0c4d9fc9cc9 Linux 6.1-rc4
> git tree: upstream
> kernel config: https://pastebin.com/raw/SBxaikiG
> C reproducer: https://pastebin.com/raw/HsDXdraZ
> verifier log: https://pastebin.com/raw/sNmSsVxs
>
> Ideally, the verifier should exit quickly in this case, since R2=42
> always holds.
> The behaviour of the verifier does not make sense to me, seems it lost
> the range information of R2.
>
> Please point out if I missed anything, the C reproducer in the link
> (https://pastebin.com/raw/HsDXdraZ)
> essentially loads the following case into `test_verifier.c`:
> {
> "BVF verifier test",
> .insns = {
> BPF_MOV64_IMM(BPF_REG_1, 42),
> BPF_MOV64_IMM(BPF_REG_2, 0),
> BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 1, 0, 1),
> BPF_EXIT_INSN(),
> BPF_ALU64_REG(BPF_ADD, BPF_REG_2, BPF_REG_1),
> BPF_ALU32_IMM(BPF_DIV, BPF_REG_2, 1),
> BPF_ALU64_IMM(BPF_SUB, BPF_REG_1, 108),
> BPF_JMP32_REG(BPF_JGT, BPF_REG_1, BPF_REG_2, -3),
> BPF_MOV64_REG(BPF_REG_0, BPF_REG_2),
> BPF_EXIT_INSN(),
> },
> .prog_type = BPF_PROG_TYPE_XDP,
> },
>
> The verifier's log is more then 4M, but essentially is:
> 0: R1=ctx(off=0,imm=0) R10=fp0
> 0: (b7) r1 = 42 ; R1_w=P42
> 1: (b7) r2 = 0 ; R2_w=P0
> 2: (85) call pc+1
> caller:
> R10=fp0
> callee:
> frame1: R1_w=P42 R2_w=P0 R10=fp0
> 4: (57) r2 &= -52 ; frame1: R2_w=P0
> 5: (0f) r2 += r1 ; frame1: R1_w=P42 R2_w=P42
> 6: (34) w2 /= 1 ; frame1:
> R2_w=Pscalar(umax=4294967295,var_off=(0x0; 0xffffffff))
> 7: (17) r1 -= 108 ; frame1: R1_w=P-66
> 8: (2e) if w1 > w2 goto pc-3 6: frame1: R1_w=P-66
> R2_w=Pscalar(umax=4294967229,var_off=(0x0; 0xffffffff)) R10=fp0
> 6: (34) w2 /= 1 ; frame1:
> R2_w=Pscalar(umax=4294967295,var_off=(0x0; 0xffffffff))
> 7: (17) r1 -= 108 ; frame1: R1_w=P-174
> 8: (2e) if w1 > w2 goto pc-3 6: frame1: R1_w=P-174
> R2_w=Pscalar(umax=4294967121,var_off=(0x0; 0xffffffff)) R10=fp0
> 6: (34) w2 /= 1 ; frame1:
> R2_w=Pscalar(umax=4294967295,var_off=(0x0; 0xffffffff))
> 7: (17) r1 -= 108 ; frame1: R1=P-282
> 8: (2e) if w1 > w2 goto pc-3 6: frame1: R1=P-282
> R2=Pscalar(umax=4294967013,var_off=(0x0; 0xffffffff)) R10=fp0
> ...
> 6: (34) w2 /= 1 ; frame1:
> R2_w=Pscalar(umax=4294967295,var_off=(0x0; 0xffffffff))
> 7: (17) r1 -= 108 ; frame1: R1_w=P-6342690
> 8: (2e) if w1 > w2 goto pc-3 6: frame1: R1_w=P-6342690
> R2_w=Pscalar(umax=4288624605,var_off=(0x0; 0xffffffff)) R10=fp0
> 6: (34) w2 /= 1 ; frame1:
> R2_w=Pscalar(umax=4294967295,var_off=(0x0; 0xffffffff))
> 7: (17) r1 -= 108 ; frame1: R1_w=P-6342798
> 8: (2e) if w1 > w2 goto pc-3 ; frame1: R1_w=P-6342798
> R2_w=Pscalar(umin=4288624498,umax=4294967295,var_off=(0xff800000;
> 0x7fffff),s32_min=-6342798,s32_max=-1)
> 9: (bf) r0 = r2 ; frame1:
> R0_w=Pscalar(id=58730,umin=4288624498,umax=4294967295,var_off=(0xff800000;
> 0x7fffff),s32_min=-6342798,s32_max=-1)
> R2_w=Pscalar(id=58730,umin=4288624498,umax=4294967295,var_off=(0xff800000;
> 0x7fffff),s32_min=-6342798,s32_max=-1)
> 10: (95) exit
> returning from callee:
> frame1: R0_w=Pscalar(id=58730,umin=4288624498,umax=4294967295,var_off=(0xff800000;
> 0x7fffff),s32_min=-6342798,s32_max=-1) R1_w=P-6342798
> R2_w=Pscalar(id=58730,umin=4288624498,umax=4294967295,var_off=(0xff800000;
> 0x7fffff),s32_min=-6342798,s32_max=-1) R10=fp0
> to caller at 3:
> R0_w=Pscalar(id=58730,umin=4288624498,umax=429496

2022-11-08 03:34:27

by Hao Sun

[permalink] [raw]
Subject: Re: BUG in BPF verifier, 10 insns costs 2 mins+ to load

Alexei Starovoitov <[email protected]> 于2022年11月8日周二 04:52写道:
>
> On Sun, Nov 6, 2022 at 8:26 PM Hao Sun <[email protected]> wrote:
> >
> > Hi,
> >
> > I've just written a BPF verifier fuzzer, targeting logic bugs in the
> > BPF verifier.
> > The following is an abnormal case it generated. The case only contains 10
> > BPF instructions but costs more than 2 mins to load on :
>
> with full verbose verifier logging, right?
> That is expected for any prog that is going to hit the 1M insn limit.
>

Hi,

Thanks for looking this.

No, `log_level` was set to 0 in this test. It takes more time if
enable verbose logging.
In this particular case, verifier should be able to exit at the first
time it meets BPF_JGT,
since R2=42 after insn 2, the test case essentially is like this:

0: r1 = 42
1: r2 = 0
2: r2 += r1
3: r2 /= 1 (alu32)
4: r1 -= 108
5: if r1 > r2 goto -3

But R2=42 was lost, after the following check for insn 3:

check_alu_op
-> adjust_scalar_min_max_vals
-> switch (op_code) {
default:
mark_reg_unknown(env, regs, insn->dst_reg);
break;
}

So, `dst_reg` range information is lost for both `BPF_DIV` and `BPF_MOD` insn.
I guess it's not easy to perform tracking over these ops in general
case, but should
we consider special cases like, devide by 1 and mod by 1, etc.

Also, I'm wondering if this behavior can be exploited, e.g., hind
actual value of particular
regs by deviding it by 1, then, trick verifier by adding a if
statement, finally, we get regs
with the value we want, something like this:

r0 = evil_val
r0 /= 1
if r0 > some_val goto X
at X:
verifier thinks r0 > some_val
but we know r0 equals evil_val

Just a random thought, no actual POC for this, any idea?

Regards
Hao

> > HEAD commit: f0c4d9fc9cc9 Linux 6.1-rc4
> > git tree: upstream
> > kernel config: https://pastebin.com/raw/SBxaikiG
> > C reproducer: https://pastebin.com/raw/HsDXdraZ
> > verifier log: https://pastebin.com/raw/sNmSsVxs
> >
> > Ideally, the verifier should exit quickly in this case, since R2=42
> > always holds.
> > The behaviour of the verifier does not make sense to me, seems it lost
> > the range information of R2.
> >
> > Please point out if I missed anything, the C reproducer in the link
> > (https://pastebin.com/raw/HsDXdraZ)
> > essentially loads the following case into `test_verifier.c`:
> > {
> > "BVF verifier test",
> > .insns = {
> > BPF_MOV64_IMM(BPF_REG_1, 42),
> > BPF_MOV64_IMM(BPF_REG_2, 0),
> > BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 1, 0, 1),
> > BPF_EXIT_INSN(),
> > BPF_ALU64_REG(BPF_ADD, BPF_REG_2, BPF_REG_1),
> > BPF_ALU32_IMM(BPF_DIV, BPF_REG_2, 1),
> > BPF_ALU64_IMM(BPF_SUB, BPF_REG_1, 108),
> > BPF_JMP32_REG(BPF_JGT, BPF_REG_1, BPF_REG_2, -3),
> > BPF_MOV64_REG(BPF_REG_0, BPF_REG_2),
> > BPF_EXIT_INSN(),
> > },
> > .prog_type = BPF_PROG_TYPE_XDP,
> > },
> >
> > The verifier's log is more then 4M, but essentially is:
> > 0: R1=ctx(off=0,imm=0) R10=fp0
> > 0: (b7) r1 = 42 ; R1_w=P42
> > 1: (b7) r2 = 0 ; R2_w=P0
> > 2: (85) call pc+1
> > caller:
> > R10=fp0
> > callee:
> > frame1: R1_w=P42 R2_w=P0 R10=fp0
> > 4: (57) r2 &= -52 ; frame1: R2_w=P0
> > 5: (0f) r2 += r1 ; frame1: R1_w=P42 R2_w=P42
> > 6: (34) w2 /= 1 ; frame1:
> > R2_w=Pscalar(umax=4294967295,var_off=(0x0; 0xffffffff))
> > 7: (17) r1 -= 108 ; frame1: R1_w=P-66
> > 8: (2e) if w1 > w2 goto pc-3 6: frame1: R1_w=P-66
> > R2_w=Pscalar(umax=4294967229,var_off=(0x0; 0xffffffff)) R10=fp0
> > 6: (34) w2 /= 1 ; frame1:
> > R2_w=Pscalar(umax=4294967295,var_off=(0x0; 0xffffffff))
> > 7: (17) r1 -= 108 ; frame1: R1_w=P-174
> > 8: (2e) if w1 > w2 goto pc-3 6: frame1: R1_w=P-174
> > R2_w=Pscalar(umax=4294967121,var_off=(0x0; 0xffffffff)) R10=fp0
> > 6: (34) w2 /= 1 ; frame1:
> > R2_w=Pscalar(umax=4294967295,var_off=(0x0; 0xffffffff))
> > 7: (17) r1 -= 108 ; frame1: R1=P-282
> > 8: (2e) if w1 > w2 goto pc-3 6: frame1: R1=P-282
> > R2=Pscalar(umax=4294967013,var_off=(0x0; 0xffffffff)) R10=fp0
> > ...
> > 6: (34) w2 /= 1 ; frame1:
> > R2_w=Pscalar(umax=4294967295,var_off=(0x0; 0xffffffff))
> > 7: (17) r1 -= 108 ; frame1: R1_w=P-6342690
> > 8: (2e) if w1 > w2 goto pc-3 6: frame1: R1_w=P-6342690
> > R2_w=Pscalar(umax=4288624605,var_off=(0x0; 0xffffffff)) R10=fp0
> > 6: (34) w2 /= 1 ; frame1:
> > R2_w=Pscalar(umax=4294967295,var_off=(0x0; 0xffffffff))
> > 7: (17) r1 -= 108 ; frame1: R1_w=P-6342798
> > 8: (2e) if w1 > w2 goto pc-3 ; frame1: R1_w=P-6342798
> > R2_w=Pscalar(umin=4288624498,umax=4294967295,var_off=(0xff800000;
> > 0x7fffff),s32_min=-6342798,s32_max=-1)
> > 9: (bf) r0 = r2 ; frame1:
> > R0_w=Pscalar(id=58730,umin=4288624498,umax=4294967295,var_off=(0xff800000;
> > 0x7fffff),s32_min=-6342798,s32_max=-1)
> > R2_w=Pscalar(id=58730,umin=4288624498,umax=4294967295,var_off=(0xff800000;
> > 0x7fffff),s32_min=-6342798,s32_max=-1)
> > 10: (95) exit
> > returning from callee:
> > frame1: R0_w=Pscalar(id=58730,umin=4288624498,umax=4294967295,var_off=(0xff800000;
> > 0x7fffff),s32_min=-6342798,s32_max=-1) R1_w=P-6342798
> > R2_w=Pscalar(id=58730,umin=4288624498,umax=4294967295,var_off=(0xff800000;
> > 0x7fffff),s32_min=-6342798,s32_max=-1) R10=fp0
> > to caller at 3:
> > R0_w=Pscalar(id=58730,umin=4288624498,umax=429496