BPF List
 help / color / mirror / Atom feed
* [PATCH bpf-next 1/2] bpf: handle fake register spill to stack with BPF_ST_MEM instruction
@ 2023-12-09  1:09 Andrii Nakryiko
  2023-12-09  1:09 ` [PATCH bpf-next 2/2] selftests/bpf: validate fake register spill/fill precision backtracking logic Andrii Nakryiko
                   ` (2 more replies)
  0 siblings, 3 replies; 10+ messages in thread
From: Andrii Nakryiko @ 2023-12-09  1:09 UTC (permalink / raw)
  To: bpf, ast, daniel, martin.lau; +Cc: andrii, kernel-team

When verifier validates BPF_ST_MEM instruction that stores known
constant to stack (e.g., *(u64 *)(r10 - 8) = 123), it effectively spills
a fake register with a constant (but initially imprecise) value to
a stack slot. Because read-side logic treats it as a proper register
fill from stack slot, we need to mark such stack slot initialization as
INSN_F_STACK_ACCESS instruction to stop precision backtracking from
missing it.

Fixes: 41f6f64e6999 ("bpf: support non-r10 register spill/fill to/from stack in precision tracking")
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
---
 kernel/bpf/verifier.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index fb690539d5f6..727a59e4a647 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -4498,7 +4498,6 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env,
 		__mark_reg_known(&fake_reg, insn->imm);
 		fake_reg.type = SCALAR_VALUE;
 		save_register_state(env, state, spi, &fake_reg, size);
-		insn_flags = 0; /* not a register spill */
 	} else if (reg && is_spillable_regtype(reg->type)) {
 		/* register containing pointer is being spilled into stack */
 		if (size != BPF_REG_SIZE) {
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH bpf-next 2/2] selftests/bpf: validate fake register spill/fill precision backtracking logic
  2023-12-09  1:09 [PATCH bpf-next 1/2] bpf: handle fake register spill to stack with BPF_ST_MEM instruction Andrii Nakryiko
@ 2023-12-09  1:09 ` Andrii Nakryiko
  2023-12-09 18:02   ` Eduard Zingerman
  2023-12-09  2:01 ` [PATCH bpf-next 1/2] bpf: handle fake register spill to stack with BPF_ST_MEM instruction Eduard Zingerman
  2023-12-10  3:20 ` patchwork-bot+netdevbpf
  2 siblings, 1 reply; 10+ messages in thread
From: Andrii Nakryiko @ 2023-12-09  1:09 UTC (permalink / raw)
  To: bpf, ast, daniel, martin.lau; +Cc: andrii, kernel-team

Add two tests validating that verifier's precision backtracking logic
handles BPF_ST_MEM instructions that produce fake register spill into
register slot. This is happening when non-zero constant is written
directly to a slot, e.g., *(u64 *)(r10 -8) = 123.

Add both full 64-bit register spill, as well as 32-bit "sub-spill".

Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
---
 .../selftests/bpf/progs/verifier_spill_fill.c | 154 ++++++++++++++++++
 1 file changed, 154 insertions(+)

diff --git a/tools/testing/selftests/bpf/progs/verifier_spill_fill.c b/tools/testing/selftests/bpf/progs/verifier_spill_fill.c
index df4920da3472..508f5d6c7347 100644
--- a/tools/testing/selftests/bpf/progs/verifier_spill_fill.c
+++ b/tools/testing/selftests/bpf/progs/verifier_spill_fill.c
@@ -577,4 +577,158 @@ __naked void partial_stack_load_preserves_zeros(void)
 	: __clobber_common);
 }
 
+char two_byte_buf[2] SEC(".data.two_byte_buf");
+
+SEC("raw_tp")
+__log_level(2) __flag(BPF_F_TEST_STATE_FREQ)
+__success
+/* make sure fp-8 is IMPRECISE fake register spill */
+__msg("3: (7a) *(u64 *)(r10 -8) = 1          ; R10=fp0 fp-8_w=1")
+/* and fp-16 is spilled IMPRECISE const reg */
+__msg("5: (7b) *(u64 *)(r10 -16) = r0        ; R0_w=1 R10=fp0 fp-16_w=1")
+/* validate load from fp-8, which was initialized using BPF_ST_MEM */
+__msg("8: (79) r2 = *(u64 *)(r10 -8)         ; R2_w=1 R10=fp0 fp-8=1")
+__msg("9: (0f) r1 += r2")
+__msg("mark_precise: frame0: last_idx 9 first_idx 7 subseq_idx -1")
+__msg("mark_precise: frame0: regs=r2 stack= before 8: (79) r2 = *(u64 *)(r10 -8)")
+__msg("mark_precise: frame0: regs= stack=-8 before 7: (bf) r1 = r6")
+/* note, fp-8 is precise, fp-16 is not yet precise, we'll get there */
+__msg("mark_precise: frame0: parent state regs= stack=-8:  R0_w=1 R1=ctx() R6_r=map_value(map=.data.two_byte_,ks=4,vs=2) R10=fp0 fp-8_rw=P1 fp-16_w=1")
+__msg("mark_precise: frame0: last_idx 6 first_idx 3 subseq_idx 7")
+__msg("mark_precise: frame0: regs= stack=-8 before 6: (05) goto pc+0")
+__msg("mark_precise: frame0: regs= stack=-8 before 5: (7b) *(u64 *)(r10 -16) = r0")
+__msg("mark_precise: frame0: regs= stack=-8 before 4: (b7) r0 = 1")
+__msg("mark_precise: frame0: regs= stack=-8 before 3: (7a) *(u64 *)(r10 -8) = 1")
+__msg("10: R1_w=map_value(map=.data.two_byte_,ks=4,vs=2,off=1) R2_w=1")
+/* validate load from fp-16, which was initialized using BPF_STX_MEM */
+__msg("12: (79) r2 = *(u64 *)(r10 -16)       ; R2_w=1 R10=fp0 fp-16=1")
+__msg("13: (0f) r1 += r2")
+__msg("mark_precise: frame0: last_idx 13 first_idx 7 subseq_idx -1")
+__msg("mark_precise: frame0: regs=r2 stack= before 12: (79) r2 = *(u64 *)(r10 -16)")
+__msg("mark_precise: frame0: regs= stack=-16 before 11: (bf) r1 = r6")
+__msg("mark_precise: frame0: regs= stack=-16 before 10: (73) *(u8 *)(r1 +0) = r2")
+__msg("mark_precise: frame0: regs= stack=-16 before 9: (0f) r1 += r2")
+__msg("mark_precise: frame0: regs= stack=-16 before 8: (79) r2 = *(u64 *)(r10 -8)")
+__msg("mark_precise: frame0: regs= stack=-16 before 7: (bf) r1 = r6")
+/* now both fp-8 and fp-16 are precise, very good */
+__msg("mark_precise: frame0: parent state regs= stack=-16:  R0_w=1 R1=ctx() R6_r=map_value(map=.data.two_byte_,ks=4,vs=2) R10=fp0 fp-8_rw=P1 fp-16_rw=P1")
+__msg("mark_precise: frame0: last_idx 6 first_idx 3 subseq_idx 7")
+__msg("mark_precise: frame0: regs= stack=-16 before 6: (05) goto pc+0")
+__msg("mark_precise: frame0: regs= stack=-16 before 5: (7b) *(u64 *)(r10 -16) = r0")
+__msg("mark_precise: frame0: regs=r0 stack= before 4: (b7) r0 = 1")
+__msg("14: R1_w=map_value(map=.data.two_byte_,ks=4,vs=2,off=1) R2_w=1")
+__naked void stack_load_preserves_const_precision(void)
+{
+	asm volatile (
+		/* establish checkpoint with state that has no stack slots;
+		 * if we bubble up to this state without finding desired stack
+		 * slot, then it's a bug and should be caught
+		 */
+		"goto +0;"
+
+		/* fp-8 is const 1 *fake* register */
+		".8byte %[fp8_st_one];" /* LLVM-18+: *(u64 *)(r10 -8) = 1; */
+
+		/* fp-16 is const 1 register */
+		"r0 = 1;"
+		"*(u64 *)(r10 -16) = r0;"
+
+		/* force checkpoint to check precision marks preserved in parent states */
+		"goto +0;"
+
+		/* load single U64 from aligned FAKE_REG=1 slot */
+		"r1 = %[two_byte_buf];"
+		"r2 = *(u64 *)(r10 -8);"
+		"r1 += r2;"
+		"*(u8 *)(r1 + 0) = r2;" /* this should be fine */
+
+		/* load single U64 from aligned REG=1 slot */
+		"r1 = %[two_byte_buf];"
+		"r2 = *(u64 *)(r10 -16);"
+		"r1 += r2;"
+		"*(u8 *)(r1 + 0) = r2;" /* this should be fine */
+
+		"r0 = 0;"
+		"exit;"
+	:
+	: __imm_ptr(two_byte_buf),
+	  __imm_insn(fp8_st_one, BPF_ST_MEM(BPF_DW, BPF_REG_FP, -8, 1))
+	: __clobber_common);
+}
+
+SEC("raw_tp")
+__log_level(2) __flag(BPF_F_TEST_STATE_FREQ)
+__success
+/* make sure fp-8 is 32-bit FAKE subregister spill */
+__msg("3: (62) *(u32 *)(r10 -8) = 1          ; R10=fp0 fp-8=????1")
+/* but fp-16 is spilled IMPRECISE zero const reg */
+__msg("5: (63) *(u32 *)(r10 -16) = r0        ; R0_w=1 R10=fp0 fp-16=????1")
+/* validate load from fp-8, which was initialized using BPF_ST_MEM */
+__msg("8: (61) r2 = *(u32 *)(r10 -8)         ; R2_w=1 R10=fp0 fp-8=????1")
+__msg("9: (0f) r1 += r2")
+__msg("mark_precise: frame0: last_idx 9 first_idx 7 subseq_idx -1")
+__msg("mark_precise: frame0: regs=r2 stack= before 8: (61) r2 = *(u32 *)(r10 -8)")
+__msg("mark_precise: frame0: regs= stack=-8 before 7: (bf) r1 = r6")
+__msg("mark_precise: frame0: parent state regs= stack=-8:  R0_w=1 R1=ctx() R6_r=map_value(map=.data.two_byte_,ks=4,vs=2) R10=fp0 fp-8_r=????P1 fp-16=????1")
+__msg("mark_precise: frame0: last_idx 6 first_idx 3 subseq_idx 7")
+__msg("mark_precise: frame0: regs= stack=-8 before 6: (05) goto pc+0")
+__msg("mark_precise: frame0: regs= stack=-8 before 5: (63) *(u32 *)(r10 -16) = r0")
+__msg("mark_precise: frame0: regs= stack=-8 before 4: (b7) r0 = 1")
+__msg("mark_precise: frame0: regs= stack=-8 before 3: (62) *(u32 *)(r10 -8) = 1")
+__msg("10: R1_w=map_value(map=.data.two_byte_,ks=4,vs=2,off=1) R2_w=1")
+/* validate load from fp-16, which was initialized using BPF_STX_MEM */
+__msg("12: (61) r2 = *(u32 *)(r10 -16)       ; R2_w=1 R10=fp0 fp-16=????1")
+__msg("13: (0f) r1 += r2")
+__msg("mark_precise: frame0: last_idx 13 first_idx 7 subseq_idx -1")
+__msg("mark_precise: frame0: regs=r2 stack= before 12: (61) r2 = *(u32 *)(r10 -16)")
+__msg("mark_precise: frame0: regs= stack=-16 before 11: (bf) r1 = r6")
+__msg("mark_precise: frame0: regs= stack=-16 before 10: (73) *(u8 *)(r1 +0) = r2")
+__msg("mark_precise: frame0: regs= stack=-16 before 9: (0f) r1 += r2")
+__msg("mark_precise: frame0: regs= stack=-16 before 8: (61) r2 = *(u32 *)(r10 -8)")
+__msg("mark_precise: frame0: regs= stack=-16 before 7: (bf) r1 = r6")
+__msg("mark_precise: frame0: parent state regs= stack=-16:  R0_w=1 R1=ctx() R6_r=map_value(map=.data.two_byte_,ks=4,vs=2) R10=fp0 fp-8_r=????P1 fp-16_r=????P1")
+__msg("mark_precise: frame0: last_idx 6 first_idx 3 subseq_idx 7")
+__msg("mark_precise: frame0: regs= stack=-16 before 6: (05) goto pc+0")
+__msg("mark_precise: frame0: regs= stack=-16 before 5: (63) *(u32 *)(r10 -16) = r0")
+__msg("mark_precise: frame0: regs=r0 stack= before 4: (b7) r0 = 1")
+__msg("14: R1_w=map_value(map=.data.two_byte_,ks=4,vs=2,off=1) R2_w=1")
+__naked void stack_load_preserves_const_precision_subreg(void)
+{
+	asm volatile (
+		/* establish checkpoint with state that has no stack slots;
+		 * if we bubble up to this state without finding desired stack
+		 * slot, then it's a bug and should be caught
+		 */
+		"goto +0;"
+
+		/* fp-8 is const 1 *fake* SUB-register */
+		".8byte %[fp8_st_one];" /* LLVM-18+: *(u32 *)(r10 -8) = 1; */
+
+		/* fp-16 is const 1 SUB-register */
+		"r0 = 1;"
+		"*(u32 *)(r10 -16) = r0;"
+
+		/* force checkpoint to check precision marks preserved in parent states */
+		"goto +0;"
+
+		/* load single U32 from aligned FAKE_REG=1 slot */
+		"r1 = %[two_byte_buf];"
+		"r2 = *(u32 *)(r10 -8);"
+		"r1 += r2;"
+		"*(u8 *)(r1 + 0) = r2;" /* this should be fine */
+
+		/* load single U32 from aligned REG=1 slot */
+		"r1 = %[two_byte_buf];"
+		"r2 = *(u32 *)(r10 -16);"
+		"r1 += r2;"
+		"*(u8 *)(r1 + 0) = r2;" /* this should be fine */
+
+		"r0 = 0;"
+		"exit;"
+	:
+	: __imm_ptr(two_byte_buf),
+	  __imm_insn(fp8_st_one, BPF_ST_MEM(BPF_W, BPF_REG_FP, -8, 1)) /* 32-bit spill */
+	: __clobber_common);
+}
+
 char _license[] SEC("license") = "GPL";
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [PATCH bpf-next 1/2] bpf: handle fake register spill to stack with BPF_ST_MEM instruction
  2023-12-09  1:09 [PATCH bpf-next 1/2] bpf: handle fake register spill to stack with BPF_ST_MEM instruction Andrii Nakryiko
  2023-12-09  1:09 ` [PATCH bpf-next 2/2] selftests/bpf: validate fake register spill/fill precision backtracking logic Andrii Nakryiko
@ 2023-12-09  2:01 ` Eduard Zingerman
  2023-12-09  2:15   ` Andrii Nakryiko
  2023-12-10  3:20 ` patchwork-bot+netdevbpf
  2 siblings, 1 reply; 10+ messages in thread
From: Eduard Zingerman @ 2023-12-09  2:01 UTC (permalink / raw)
  To: Andrii Nakryiko, bpf, ast, daniel, martin.lau; +Cc: kernel-team

On Fri, 2023-12-08 at 17:09 -0800, Andrii Nakryiko wrote:
> When verifier validates BPF_ST_MEM instruction that stores known
> constant to stack (e.g., *(u64 *)(r10 - 8) = 123), it effectively spills
> a fake register with a constant (but initially imprecise) value to
> a stack slot. Because read-side logic treats it as a proper register
> fill from stack slot, we need to mark such stack slot initialization as
> INSN_F_STACK_ACCESS instruction to stop precision backtracking from
> missing it.
> 
> Fixes: 41f6f64e6999 ("bpf: support non-r10 register spill/fill to/from stack in precision tracking")
> Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
> ---
>  kernel/bpf/verifier.c | 1 -
>  1 file changed, 1 deletion(-)
> 
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index fb690539d5f6..727a59e4a647 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -4498,7 +4498,6 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env,
>  		__mark_reg_known(&fake_reg, insn->imm);
>  		fake_reg.type = SCALAR_VALUE;
>  		save_register_state(env, state, spi, &fake_reg, size);
> -		insn_flags = 0; /* not a register spill */
>  	} else if (reg && is_spillable_regtype(reg->type)) {
>  		/* register containing pointer is being spilled into stack */
>  		if (size != BPF_REG_SIZE) {

So, the problem is that for some 'r5 = r10; *(u64 *)(r5 - 8) = 123'
backtracking won't reset precision mark for -8, right?
That's not a tragedy we just get more precision marks than needed,
however, I think that same logic applies to the MISC/ZERO case below.
I'll look through the tests in the morning.

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH bpf-next 1/2] bpf: handle fake register spill to stack with BPF_ST_MEM instruction
  2023-12-09  2:01 ` [PATCH bpf-next 1/2] bpf: handle fake register spill to stack with BPF_ST_MEM instruction Eduard Zingerman
@ 2023-12-09  2:15   ` Andrii Nakryiko
  2023-12-09  2:16     ` Andrii Nakryiko
                       ` (2 more replies)
  0 siblings, 3 replies; 10+ messages in thread
From: Andrii Nakryiko @ 2023-12-09  2:15 UTC (permalink / raw)
  To: Eduard Zingerman
  Cc: Andrii Nakryiko, bpf, ast, daniel, martin.lau, kernel-team

On Fri, Dec 8, 2023 at 6:01 PM Eduard Zingerman <eddyz87@gmail.com> wrote:
>
> On Fri, 2023-12-08 at 17:09 -0800, Andrii Nakryiko wrote:
> > When verifier validates BPF_ST_MEM instruction that stores known
> > constant to stack (e.g., *(u64 *)(r10 - 8) = 123), it effectively spills
> > a fake register with a constant (but initially imprecise) value to
> > a stack slot. Because read-side logic treats it as a proper register
> > fill from stack slot, we need to mark such stack slot initialization as
> > INSN_F_STACK_ACCESS instruction to stop precision backtracking from
> > missing it.
> >
> > Fixes: 41f6f64e6999 ("bpf: support non-r10 register spill/fill to/from stack in precision tracking")
> > Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
> > ---
> >  kernel/bpf/verifier.c | 1 -
> >  1 file changed, 1 deletion(-)
> >
> > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> > index fb690539d5f6..727a59e4a647 100644
> > --- a/kernel/bpf/verifier.c
> > +++ b/kernel/bpf/verifier.c
> > @@ -4498,7 +4498,6 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env,
> >               __mark_reg_known(&fake_reg, insn->imm);
> >               fake_reg.type = SCALAR_VALUE;
> >               save_register_state(env, state, spi, &fake_reg, size);
> > -             insn_flags = 0; /* not a register spill */
> >       } else if (reg && is_spillable_regtype(reg->type)) {
> >               /* register containing pointer is being spilled into stack */
> >               if (size != BPF_REG_SIZE) {
>
> So, the problem is that for some 'r5 = r10; *(u64 *)(r5 - 8) = 123'
> backtracking won't reset precision mark for -8, right?

no, the problem is that we won't stop at the right instruction. Let's
say we have this sequence

1: *(u64 *)(r10 - 8) = 123;
2: r1 = *(u64 *)(r10 - 8);
3: if r1 == 123 goto +10;
...

At 3: we want to set r1 to precise. We go back, see that at 2: we set
r1 from fp-8 slot, so instead of looking for r1, we start looking for
what set fp-8 now. So we go to 1:, and because it actually is not
marked as INSN_F_STACK_ACCESS, we skip it, and keep looking further
for what set fp-8. At some point we can go to parent state that didn't
even have fp-8 stack slot allocated (or we can get out and then see
that we haven't cleared all stack slot bits in our masks). So this
patch makes it so that 1: is marked as setting fp-8 slot, and
precision propagation will clear fp-8 from the mask.

Now, the subtle thing here is that this doesn't happen with STACK_ZERO
or STACK_MISC. Let's look at STACK_MISC/STACK_INVALID case.

1: *(u8 *)(r10 -1) = 123; /* now fp-8=m??????? */
2: r1 = *(u64 *)(r10 - 8); /* STACK_MISC read, r1 is set to unknown scalar */
3: if r1 == 123 goto +10;

Let's do analysis again. At 3: we mark r1 as precise, go back to 2:.
Here 2: instruction is not marked as INSN_F_STACK_ACCESS because it
wasn't stack fill due to STACK_MISC (that's handled in
check_read_fixed_off logic). So mark_chain_precision() stops here
because that instruction is resetting r1, so we clear r1 from the
mask, but this instruction isn't STACK_ACCESS, so we don't look for
fp-8 here.

I think check_stack_write_fixed_off() can always set
INSN_F_STACK_ACCESS, actually, maybe that would be easier to follow.
Even when we write STACK_ZERO/STACK_MISC. It's only
check_stack_read_fixed_off() that has to be careful and drop
INSN_F_STACK_ACCESS if we didn't really fill the register state from
the stack slot.


> That's not a tragedy we just get more precision marks than needed,
> however, I think that same logic applies to the MISC/ZERO case below.

See above, MISC/ZERO is fine as is due to check_stack_read_fixed_off()
not setting STACK_ACCESS bit, but I can also send a version that
unconditionally sets INSNS_F_STACK_ACCESS in
check_stack_write_fixed_off().

> I'll look through the tests in the morning.

Thanks, no rush!

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH bpf-next 1/2] bpf: handle fake register spill to stack with BPF_ST_MEM instruction
  2023-12-09  2:15   ` Andrii Nakryiko
@ 2023-12-09  2:16     ` Andrii Nakryiko
  2023-12-09  2:28     ` Alexei Starovoitov
  2023-12-09 17:05     ` Eduard Zingerman
  2 siblings, 0 replies; 10+ messages in thread
From: Andrii Nakryiko @ 2023-12-09  2:16 UTC (permalink / raw)
  To: Eduard Zingerman
  Cc: Andrii Nakryiko, bpf, ast, daniel, martin.lau, kernel-team

On Fri, Dec 8, 2023 at 6:15 PM Andrii Nakryiko
<andrii.nakryiko@gmail.com> wrote:
>
> On Fri, Dec 8, 2023 at 6:01 PM Eduard Zingerman <eddyz87@gmail.com> wrote:
> >
> > On Fri, 2023-12-08 at 17:09 -0800, Andrii Nakryiko wrote:
> > > When verifier validates BPF_ST_MEM instruction that stores known
> > > constant to stack (e.g., *(u64 *)(r10 - 8) = 123), it effectively spills
> > > a fake register with a constant (but initially imprecise) value to
> > > a stack slot. Because read-side logic treats it as a proper register
> > > fill from stack slot, we need to mark such stack slot initialization as
> > > INSN_F_STACK_ACCESS instruction to stop precision backtracking from
> > > missing it.
> > >
> > > Fixes: 41f6f64e6999 ("bpf: support non-r10 register spill/fill to/from stack in precision tracking")
> > > Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
> > > ---
> > >  kernel/bpf/verifier.c | 1 -
> > >  1 file changed, 1 deletion(-)
> > >
> > > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> > > index fb690539d5f6..727a59e4a647 100644
> > > --- a/kernel/bpf/verifier.c
> > > +++ b/kernel/bpf/verifier.c
> > > @@ -4498,7 +4498,6 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env,
> > >               __mark_reg_known(&fake_reg, insn->imm);
> > >               fake_reg.type = SCALAR_VALUE;
> > >               save_register_state(env, state, spi, &fake_reg, size);
> > > -             insn_flags = 0; /* not a register spill */
> > >       } else if (reg && is_spillable_regtype(reg->type)) {
> > >               /* register containing pointer is being spilled into stack */
> > >               if (size != BPF_REG_SIZE) {
> >
> > So, the problem is that for some 'r5 = r10; *(u64 *)(r5 - 8) = 123'
> > backtracking won't reset precision mark for -8, right?
>
> no, the problem is that we won't stop at the right instruction. Let's
> say we have this sequence
>
> 1: *(u64 *)(r10 - 8) = 123;
> 2: r1 = *(u64 *)(r10 - 8);
> 3: if r1 == 123 goto +10;
> ...
>
> At 3: we want to set r1 to precise. We go back, see that at 2: we set
> r1 from fp-8 slot, so instead of looking for r1, we start looking for
> what set fp-8 now. So we go to 1:, and because it actually is not
> marked as INSN_F_STACK_ACCESS, we skip it, and keep looking further
> for what set fp-8. At some point we can go to parent state that didn't
> even have fp-8 stack slot allocated (or we can get out and then see
> that we haven't cleared all stack slot bits in our masks). So this
> patch makes it so that 1: is marked as setting fp-8 slot, and
> precision propagation will clear fp-8 from the mask.
>
> Now, the subtle thing here is that this doesn't happen with STACK_ZERO
> or STACK_MISC. Let's look at STACK_MISC/STACK_INVALID case.
>
> 1: *(u8 *)(r10 -1) = 123; /* now fp-8=m??????? */
> 2: r1 = *(u64 *)(r10 - 8); /* STACK_MISC read, r1 is set to unknown scalar */
> 3: if r1 == 123 goto +10;

small correction, with STACK_MISC conditional jump won't mark_precise,
we'd need some other instruction to trigger precision, but hopefully
the point is still clear

>
> Let's do analysis again. At 3: we mark r1 as precise, go back to 2:.
> Here 2: instruction is not marked as INSN_F_STACK_ACCESS because it
> wasn't stack fill due to STACK_MISC (that's handled in
> check_read_fixed_off logic). So mark_chain_precision() stops here
> because that instruction is resetting r1, so we clear r1 from the
> mask, but this instruction isn't STACK_ACCESS, so we don't look for
> fp-8 here.
>
> I think check_stack_write_fixed_off() can always set
> INSN_F_STACK_ACCESS, actually, maybe that would be easier to follow.
> Even when we write STACK_ZERO/STACK_MISC. It's only
> check_stack_read_fixed_off() that has to be careful and drop
> INSN_F_STACK_ACCESS if we didn't really fill the register state from
> the stack slot.
>
>
> > That's not a tragedy we just get more precision marks than needed,
> > however, I think that same logic applies to the MISC/ZERO case below.
>
> See above, MISC/ZERO is fine as is due to check_stack_read_fixed_off()
> not setting STACK_ACCESS bit, but I can also send a version that
> unconditionally sets INSNS_F_STACK_ACCESS in
> check_stack_write_fixed_off().
>
> > I'll look through the tests in the morning.
>
> Thanks, no rush!

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH bpf-next 1/2] bpf: handle fake register spill to stack with BPF_ST_MEM instruction
  2023-12-09  2:15   ` Andrii Nakryiko
  2023-12-09  2:16     ` Andrii Nakryiko
@ 2023-12-09  2:28     ` Alexei Starovoitov
  2023-12-09  4:44       ` Andrii Nakryiko
  2023-12-09 17:05     ` Eduard Zingerman
  2 siblings, 1 reply; 10+ messages in thread
From: Alexei Starovoitov @ 2023-12-09  2:28 UTC (permalink / raw)
  To: Andrii Nakryiko
  Cc: Eduard Zingerman, Andrii Nakryiko, bpf, Alexei Starovoitov,
	Daniel Borkmann, Martin KaFai Lau, Kernel Team

On Fri, Dec 8, 2023 at 6:15 PM Andrii Nakryiko
<andrii.nakryiko@gmail.com> wrote:
>
> See above, MISC/ZERO is fine as is due to check_stack_read_fixed_off()
> not setting STACK_ACCESS bit, but I can also send a version that
> unconditionally sets INSNS_F_STACK_ACCESS in
> check_stack_write_fixed_off().

but it will significantly increase jmp_history and make
backtracking slower?

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH bpf-next 1/2] bpf: handle fake register spill to stack with BPF_ST_MEM instruction
  2023-12-09  2:28     ` Alexei Starovoitov
@ 2023-12-09  4:44       ` Andrii Nakryiko
  0 siblings, 0 replies; 10+ messages in thread
From: Andrii Nakryiko @ 2023-12-09  4:44 UTC (permalink / raw)
  To: Alexei Starovoitov
  Cc: Eduard Zingerman, Andrii Nakryiko, bpf, Alexei Starovoitov,
	Daniel Borkmann, Martin KaFai Lau, Kernel Team

On Fri, Dec 8, 2023 at 6:28 PM Alexei Starovoitov
<alexei.starovoitov@gmail.com> wrote:
>
> On Fri, Dec 8, 2023 at 6:15 PM Andrii Nakryiko
> <andrii.nakryiko@gmail.com> wrote:
> >
> > See above, MISC/ZERO is fine as is due to check_stack_read_fixed_off()
> > not setting STACK_ACCESS bit, but I can also send a version that
> > unconditionally sets INSNS_F_STACK_ACCESS in
> > check_stack_write_fixed_off().
>
> but it will significantly increase jmp_history and make
> backtracking slower?

Good point about jmp_history length, so I guess we can leave the patch as is.

I don't think it will make backtracking slower, though, the algorithm
will process the same amount of instructions regardless of jmp_history
length.

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH bpf-next 1/2] bpf: handle fake register spill to stack with BPF_ST_MEM instruction
  2023-12-09  2:15   ` Andrii Nakryiko
  2023-12-09  2:16     ` Andrii Nakryiko
  2023-12-09  2:28     ` Alexei Starovoitov
@ 2023-12-09 17:05     ` Eduard Zingerman
  2 siblings, 0 replies; 10+ messages in thread
From: Eduard Zingerman @ 2023-12-09 17:05 UTC (permalink / raw)
  To: Andrii Nakryiko
  Cc: Andrii Nakryiko, bpf, ast, daniel, martin.lau, kernel-team

On Fri, 2023-12-08 at 18:15 -0800, Andrii Nakryiko wrote:
[...]
> Now, the subtle thing here is that this doesn't happen with STACK_ZERO
> or STACK_MISC. Let's look at STACK_MISC/STACK_INVALID case.
> 
> 1: *(u8 *)(r10 -1) = 123; /* now fp-8=m??????? */
> 2: r1 = *(u64 *)(r10 - 8); /* STACK_MISC read, r1 is set to unknown scalar */
> 3: if r1 == 123 goto +10;
> 
> Let's do analysis again. At 3: we mark r1 as precise, go back to 2:.
> Here 2: instruction is not marked as INSN_F_STACK_ACCESS because it
> wasn't stack fill due to STACK_MISC (that's handled in
> check_read_fixed_off logic). So mark_chain_precision() stops here
> because that instruction is resetting r1, so we clear r1 from the
> mask, but this instruction isn't STACK_ACCESS, so we don't look for
> fp-8 here.

Ok, so STACK_MISC does not actually leak any information, when misc
byte read it's still full range. Makes sense.
I think STACK_ZERO handling is fine, there is no need remember it as
stack access, as it marks precision right away.

Thank you for explanation and sorry for false alarm.

Acked-by: Eduard Zingerman <eddyz87@gmail.com>

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH bpf-next 2/2] selftests/bpf: validate fake register spill/fill precision backtracking logic
  2023-12-09  1:09 ` [PATCH bpf-next 2/2] selftests/bpf: validate fake register spill/fill precision backtracking logic Andrii Nakryiko
@ 2023-12-09 18:02   ` Eduard Zingerman
  0 siblings, 0 replies; 10+ messages in thread
From: Eduard Zingerman @ 2023-12-09 18:02 UTC (permalink / raw)
  To: Andrii Nakryiko, bpf, ast, daniel, martin.lau; +Cc: kernel-team

On Fri, 2023-12-08 at 17:09 -0800, Andrii Nakryiko wrote:
> Add two tests validating that verifier's precision backtracking logic
> handles BPF_ST_MEM instructions that produce fake register spill into
> register slot. This is happening when non-zero constant is written
> directly to a slot, e.g., *(u64 *)(r10 -8) = 123.
> 
> Add both full 64-bit register spill, as well as 32-bit "sub-spill".
> 
> Signed-off-by: Andrii Nakryiko <andrii@kernel.org>

Acked-by: Eduard Zingerman <eddyz87@gmail.com>

(imho, goto +0 is an overkill, as we should have tests for behavior of
 parent states but not that it would make test cases much simpler).


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH bpf-next 1/2] bpf: handle fake register spill to stack with BPF_ST_MEM instruction
  2023-12-09  1:09 [PATCH bpf-next 1/2] bpf: handle fake register spill to stack with BPF_ST_MEM instruction Andrii Nakryiko
  2023-12-09  1:09 ` [PATCH bpf-next 2/2] selftests/bpf: validate fake register spill/fill precision backtracking logic Andrii Nakryiko
  2023-12-09  2:01 ` [PATCH bpf-next 1/2] bpf: handle fake register spill to stack with BPF_ST_MEM instruction Eduard Zingerman
@ 2023-12-10  3:20 ` patchwork-bot+netdevbpf
  2 siblings, 0 replies; 10+ messages in thread
From: patchwork-bot+netdevbpf @ 2023-12-10  3:20 UTC (permalink / raw)
  To: Andrii Nakryiko; +Cc: bpf, ast, daniel, martin.lau, kernel-team

Hello:

This series was applied to bpf/bpf-next.git (master)
by Alexei Starovoitov <ast@kernel.org>:

On Fri, 8 Dec 2023 17:09:57 -0800 you wrote:
> When verifier validates BPF_ST_MEM instruction that stores known
> constant to stack (e.g., *(u64 *)(r10 - 8) = 123), it effectively spills
> a fake register with a constant (but initially imprecise) value to
> a stack slot. Because read-side logic treats it as a proper register
> fill from stack slot, we need to mark such stack slot initialization as
> INSN_F_STACK_ACCESS instruction to stop precision backtracking from
> missing it.
> 
> [...]

Here is the summary with links:
  - [bpf-next,1/2] bpf: handle fake register spill to stack with BPF_ST_MEM instruction
    https://git.kernel.org/bpf/bpf-next/c/482d548d40b0
  - [bpf-next,2/2] selftests/bpf: validate fake register spill/fill precision backtracking logic
    https://git.kernel.org/bpf/bpf-next/c/7d8ed51bcb32

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2023-12-10  3:20 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2023-12-09  1:09 [PATCH bpf-next 1/2] bpf: handle fake register spill to stack with BPF_ST_MEM instruction Andrii Nakryiko
2023-12-09  1:09 ` [PATCH bpf-next 2/2] selftests/bpf: validate fake register spill/fill precision backtracking logic Andrii Nakryiko
2023-12-09 18:02   ` Eduard Zingerman
2023-12-09  2:01 ` [PATCH bpf-next 1/2] bpf: handle fake register spill to stack with BPF_ST_MEM instruction Eduard Zingerman
2023-12-09  2:15   ` Andrii Nakryiko
2023-12-09  2:16     ` Andrii Nakryiko
2023-12-09  2:28     ` Alexei Starovoitov
2023-12-09  4:44       ` Andrii Nakryiko
2023-12-09 17:05     ` Eduard Zingerman
2023-12-10  3:20 ` patchwork-bot+netdevbpf

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox