From: John Fastabend <john.fastabend@gmail.com>
To: Daniel Borkmann <daniel@iogearbox.net>, bpf@vger.kernel.org
Cc: alexei.starovoitov@gmail.com, jjlopezjaimez@google.com,
andrii.nakryiko@gmail.com, eddyz87@gmail.com,
john.fastabend@gmail.com,
Daniel Borkmann <daniel@iogearbox.net>
Subject: RE: [PATCH bpf v2 1/3] bpf: Fix reg_set_min_max corruption of fake_reg
Date: Thu, 13 Jun 2024 09:44:48 -0700 [thread overview]
Message-ID: <666b220096404_1f0f208ea@john.notmuch> (raw)
In-Reply-To: <20240613115310.25383-1-daniel@iogearbox.net>
Daniel Borkmann wrote:
> Juan reported that after doing some changes to buzzer [0] and implementing
> a new fuzzing strategy guided by coverage, they noticed the following in
> one of the probes:
>
> [...]
> 13: (79) r6 = *(u64 *)(r0 +0) ; R0=map_value(ks=4,vs=8) R6_w=scalar()
> 14: (b7) r0 = 0 ; R0_w=0
> 15: (b4) w0 = -1 ; R0_w=0xffffffff
> 16: (74) w0 >>= 1 ; R0_w=0x7fffffff
> 17: (5c) w6 &= w0 ; R0_w=0x7fffffff R6_w=scalar(smin=smin32=0,smax=umax=umax32=0x7fffffff,var_off=(0x0; 0x7fffffff))
> 18: (44) w6 |= 2 ; R6_w=scalar(smin=umin=smin32=umin32=2,smax=umax=umax32=0x7fffffff,var_off=(0x2; 0x7ffffffd))
> 19: (56) if w6 != 0x7ffffffd goto pc+1
> REG INVARIANTS VIOLATION (true_reg2): range bounds violation u64=[0x7fffffff, 0x7ffffffd] s64=[0x7fffffff, 0x7ffffffd] u32=[0x7fffffff, 0x7ffffffd] s32=[0x7fffffff, 0x7ffffffd] var_off=(0x7fffffff, 0x0)
> REG INVARIANTS VIOLATION (false_reg1): range bounds violation u64=[0x7fffffff, 0x7ffffffd] s64=[0x7fffffff, 0x7ffffffd] u32=[0x7fffffff, 0x7ffffffd] s32=[0x7fffffff, 0x7ffffffd] var_off=(0x7fffffff, 0x0)
> REG INVARIANTS VIOLATION (false_reg2): const tnum out of sync with range bounds u64=[0x0, 0xffffffffffffffff] s64=[0x8000000000000000, 0x7fffffffffffffff] u32=[0x0, 0xffffffff] s32=[0x80000000, 0x7fffffff] var_off=(0x7fffffff, 0x0)
> 19: R6_w=0x7fffffff
> 20: (95) exit
>
> from 19 to 21: R0=0x7fffffff R6=scalar(smin=umin=smin32=umin32=2,smax=umax=smax32=umax32=0x7ffffffe,var_off=(0x2; 0x7ffffffd)) R7=map_ptr(ks=4,vs=8) R9=ctx() R10=fp0 fp-24=map_ptr(ks=4,vs=8) fp-40=mmmmmmmm
> 21: R0=0x7fffffff R6=scalar(smin=umin=smin32=umin32=2,smax=umax=smax32=umax32=0x7ffffffe,var_off=(0x2; 0x7ffffffd)) R7=map_ptr(ks=4,vs=8) R9=ctx() R10=fp0 fp-24=map_ptr(ks=4,vs=8) fp-40=mmmmmmmm
> 21: (14) w6 -= 2147483632 ; R6_w=scalar(smin=umin=umin32=2,smax=umax=0xffffffff,smin32=0x80000012,smax32=14,var_off=(0x2; 0xfffffffd))
> 22: (76) if w6 s>= 0xe goto pc+1 ; R6_w=scalar(smin=umin=umin32=2,smax=umax=0xffffffff,smin32=0x80000012,smax32=13,var_off=(0x2; 0xfffffffd))
> 23: (95) exit
>
> from 22 to 24: R0=0x7fffffff R6_w=14 R7=map_ptr(ks=4,vs=8) R9=ctx() R10=fp0 fp-24=map_ptr(ks=4,vs=8) fp-40=mmmmmmmm
> 24: R0=0x7fffffff R6_w=14 R7=map_ptr(ks=4,vs=8) R9=ctx() R10=fp0 fp-24=map_ptr(ks=4,vs=8) fp-40=mmmmmmmm
> 24: (14) w6 -= 14 ; R6_w=0
> [...]
>
> What can be seen here is a register invariant violation on line 19. After
> the binary-or in line 18, the verifier knows that bit 2 is set but knows
> nothing about the rest of the content which was loaded from a map value,
> meaning, range is [2,0x7fffffff] with var_off=(0x2; 0x7ffffffd). When in
> line 19 the verifier analyzes the branch, it splits the register states
> in reg_set_min_max() into the registers of the true branch (true_reg1,
> true_reg2) and the registers of the false branch (false_reg1, false_reg2).
>
> Since the test is w6 != 0x7ffffffd, the src_reg is a known constant.
> Internally, the verifier creates a "fake" register initialized as scalar
> to the value of 0x7ffffffd, and then passes it onto reg_set_min_max(). Now,
> for line 19, it is mathematically impossible to take the false branch of
> this program, yet the verifier analyzes it. It is impossible because the
> second bit of r6 will be set due to the prior or operation and the
> constant in the condition has that bit unset (hex(fd) == binary(1111 1101).
>
> When the verifier first analyzes the false / fall-through branch, it will
> compute an intersection between the var_off of r6 and of the constant. This
> is because the verifier creates a "fake" register initialized to the value
> of the constant. The intersection result later refines both registers in
> regs_refine_cond_op():
>
> [...]
> t = tnum_intersect(tnum_subreg(reg1->var_off), tnum_subreg(reg2->var_off));
> reg1->var_off = tnum_with_subreg(reg1->var_off, t);
> reg2->var_off = tnum_with_subreg(reg2->var_off, t);
> [...]
>
> Since the verifier is analyzing the false branch of the conditional jump,
> reg1 is equal to false_reg1 and reg2 is equal to false_reg2, i.e. the reg2
> is the "fake" register that was meant to hold a constant value. The resulting
> var_off of the intersection says that both registers now hold a known value
> of var_off=(0x7fffffff, 0x0) or in other words: this operation manages to
> make the verifier think that the "constant" value that was passed in the
> jump operation now holds a different value.
>
> Normally this would not be an issue since it should not influence the true
> branch, however, false_reg2 and true_reg2 are pointers to the same "fake"
> register. Meaning, the false branch can influence the results of the true
> branch. In line 24, the verifier assumes R6_w=0, but the actual runtime
> value in this case is 1. The fix is simply not passing in the same "fake"
> register location as inputs to reg_set_min_max(), but instead making a
> copy. Moving the fake_reg into the env also reduces stack consumption by
> 120 bytes. With this, the verifier successfully rejects invalid accesses
> from the test program.
>
> [0] https://github.com/google/buzzer
>
> Fixes: 67420501e868 ("bpf: generalize reg_set_min_max() to handle non-const register comparisons")
> Reported-by: Juan José López Jaimez <jjlopezjaimez@google.com>
> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
> ---
> v1 -> v2:
> - Reduce stack space consumption (Alexei)
Reviewed-by: John Fastabend <john.fastabend@gmail.com>
next prev parent reply other threads:[~2024-06-13 16:44 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-06-13 11:53 [PATCH bpf v2 1/3] bpf: Fix reg_set_min_max corruption of fake_reg Daniel Borkmann
2024-06-13 11:53 ` [PATCH bpf v2 2/3] bpf: Reduce stack consumption in check_stack_write_fixed_off Daniel Borkmann
2024-06-13 11:53 ` [PATCH bpf v2 3/3] selftests/bpf: Add test coverage for reg_set_min_max handling Daniel Borkmann
2024-06-13 16:44 ` John Fastabend [this message]
2024-06-13 18:20 ` [PATCH bpf v2 1/3] bpf: Fix reg_set_min_max corruption of fake_reg patchwork-bot+netdevbpf
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=666b220096404_1f0f208ea@john.notmuch \
--to=john.fastabend@gmail.com \
--cc=alexei.starovoitov@gmail.com \
--cc=andrii.nakryiko@gmail.com \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=jjlopezjaimez@google.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox