On Fri, Dec 8, 2023 at 6:01 PM Eduard Zingerman <eddyz87@xxxxxxxxx> 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@xxxxxxxxxx> > > --- > > 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!