* [PATCH RFC bpf-next 0/2] propagate nullness information for reg to reg comparisons
@ 2022-08-22 9:43 Eduard Zingerman
2022-08-22 9:43 ` [PATCH RFC bpf-next 1/2] bpf: " Eduard Zingerman
2022-08-22 9:43 ` [PATCH RFC bpf-next 2/2] selftests/bpf: check nullness propagation " Eduard Zingerman
0 siblings, 2 replies; 11+ messages in thread
From: Eduard Zingerman @ 2022-08-22 9:43 UTC (permalink / raw)
To: bpf, ast, andrii, daniel, kernel-team, yhs; +Cc: Eduard Zingerman
Hi Everyone,
This patchset adds ability to propagates nullness information for
branches of register to register equality compare instructions. The
following rules are used:
- suppose register A maybe null
- suppose register B is not null
- for JNE A, B, ... - A is not null in the false branch
- for JEQ A, B, ... - A is not null in the true branch
E.g. for program like below:
r6 = skb->sk;
r7 = sk_fullsock(r6);
r0 = sk_fullsock(r6);
if (r0 == 0) return 0; (a)
if (r0 != r7) return 0; (b)
*r7->type; (c)
return 0;
It is safe to dereference r7 at point (c), because of (a) and (b).
The utility of this change came up while working on BPF CLang backend
issue [1]. Specifically, while debugging issue with selftest
`test_sk_lookup.c`. This test has the following structure:
int access_ctx_sk(struct bpf_sk_lookup *ctx __CTX__)
{
struct bpf_sock *sk1 = NULL, *sk2 = NULL;
...
sk1 = bpf_map_lookup_elem(&redir_map, &KEY_SERVER_A);
if (!sk1) // (a)
goto out;
...
if (ctx->sk != sk1) // (b)
goto out;
...
if (ctx->sk->family != AF_INET || // (c)
ctx->sk->type != SOCK_STREAM ||
ctx->sk->state != BPF_TCP_LISTEN)
goto out;
...
}
- at (a) `sk1` is checked to be not null;
- at (b) `ctx->sk` is verified to be equal to `sk1`;
- at (c) `ctx->sk` is accessed w/o nullness check.
Currently Global Value Numbering pass considers expressions `sk1` and
`ctx->sk` to be identical at point (c) and replaces `ctx->sk` with
`sk1` (not expressions themselves but corresponding SSA values).
Since `sk1` is known to be not null after (b) verifier allows
execution of the program.
However, such optimization is not guaranteed to happen. When it does
not happen verifier reports an error.
[1] https://reviews.llvm.org/D131633#3722231
Thanks,
Eduard
Eduard Zingerman (2):
bpf: propagate nullness information for reg to reg comparisons
selftests/bpf: check nullness propagation for reg to reg comparisons
kernel/bpf/verifier.c | 39 +++-
.../bpf/verifier/jeq_infer_not_null.c | 186 ++++++++++++++++++
2 files changed, 224 insertions(+), 1 deletion(-)
create mode 100644 tools/testing/selftests/bpf/verifier/jeq_infer_not_null.c
Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
--
2.37.1
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH RFC bpf-next 1/2] bpf: propagate nullness information for reg to reg comparisons 2022-08-22 9:43 [PATCH RFC bpf-next 0/2] propagate nullness information for reg to reg comparisons Eduard Zingerman @ 2022-08-22 9:43 ` Eduard Zingerman 2022-08-23 23:15 ` John Fastabend 2022-08-25 2:34 ` Yonghong Song 2022-08-22 9:43 ` [PATCH RFC bpf-next 2/2] selftests/bpf: check nullness propagation " Eduard Zingerman 1 sibling, 2 replies; 11+ messages in thread From: Eduard Zingerman @ 2022-08-22 9:43 UTC (permalink / raw) To: bpf, ast, andrii, daniel, kernel-team, yhs; +Cc: Eduard Zingerman Propagate nullness information for branches of register to register equality compare instructions. The following rules are used: - suppose register A maybe null - suppose register B is not null - for JNE A, B, ... - A is not null in the false branch - for JEQ A, B, ... - A is not null in the true branch E.g. for program like below: r6 = skb->sk; r7 = sk_fullsock(r6); r0 = sk_fullsock(r6); if (r0 == 0) return 0; (a) if (r0 != r7) return 0; (b) *r7->type; (c) return 0; It is safe to dereference r7 at point (c), because of (a) and (b). Signed-off-by: Eduard Zingerman <eddyz87@gmail.com> --- kernel/bpf/verifier.c | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 2c1f8069f7b7..c48d34625bfd 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -472,6 +472,11 @@ static bool type_may_be_null(u32 type) return type & PTR_MAYBE_NULL; } +static bool type_is_pointer(enum bpf_reg_type type) +{ + return type != NOT_INIT && type != SCALAR_VALUE; +} + static bool is_acquire_function(enum bpf_func_id func_id, const struct bpf_map *map) { @@ -10046,6 +10051,7 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env, struct bpf_verifier_state *other_branch; struct bpf_reg_state *regs = this_branch->frame[this_branch->curframe]->regs; struct bpf_reg_state *dst_reg, *other_branch_regs, *src_reg = NULL; + struct bpf_reg_state *eq_branch_regs; u8 opcode = BPF_OP(insn->code); bool is_jmp32; int pred = -1; @@ -10155,7 +10161,7 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env, /* detect if we are comparing against a constant value so we can adjust * our min/max values for our dst register. * this is only legit if both are scalars (or pointers to the same - * object, I suppose, but we don't support that right now), because + * object, I suppose, see the next if block), because * otherwise the different base pointers mean the offsets aren't * comparable. */ @@ -10199,6 +10205,37 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env, opcode, is_jmp32); } + /* if one pointer register is compared to another pointer + * register check if PTR_MAYBE_NULL could be lifted. + * E.g. register A - maybe null + * register B - not null + * for JNE A, B, ... - A is not null in the false branch; + * for JEQ A, B, ... - A is not null in the true branch. + */ + if (!is_jmp32 && + BPF_SRC(insn->code) == BPF_X && + type_is_pointer(src_reg->type) && type_is_pointer(dst_reg->type) && + type_may_be_null(src_reg->type) != type_may_be_null(dst_reg->type)) { + eq_branch_regs = NULL; + switch (opcode) { + case BPF_JEQ: + eq_branch_regs = other_branch_regs; + break; + case BPF_JNE: + eq_branch_regs = regs; + break; + default: + /* do nothing */ + break; + } + if (eq_branch_regs) { + if (type_may_be_null(src_reg->type)) + mark_ptr_not_null_reg(&eq_branch_regs[insn->src_reg]); + else + mark_ptr_not_null_reg(&eq_branch_regs[insn->dst_reg]); + } + } + if (dst_reg->type == SCALAR_VALUE && dst_reg->id && !WARN_ON_ONCE(dst_reg->id != other_branch_regs[insn->dst_reg].id)) { find_equal_scalars(this_branch, dst_reg); -- 2.37.1 ^ permalink raw reply related [flat|nested] 11+ messages in thread
* RE: [PATCH RFC bpf-next 1/2] bpf: propagate nullness information for reg to reg comparisons 2022-08-22 9:43 ` [PATCH RFC bpf-next 1/2] bpf: " Eduard Zingerman @ 2022-08-23 23:15 ` John Fastabend 2022-08-24 22:05 ` Eduard Zingerman 2022-08-25 2:55 ` Yonghong Song 2022-08-25 2:34 ` Yonghong Song 1 sibling, 2 replies; 11+ messages in thread From: John Fastabend @ 2022-08-23 23:15 UTC (permalink / raw) To: Eduard Zingerman, bpf, ast, andrii, daniel, kernel-team, yhs Cc: Eduard Zingerman Eduard Zingerman wrote: > Propagate nullness information for branches of register to register > equality compare instructions. The following rules are used: > - suppose register A maybe null > - suppose register B is not null > - for JNE A, B, ... - A is not null in the false branch > - for JEQ A, B, ... - A is not null in the true branch > > E.g. for program like below: > > r6 = skb->sk; > r7 = sk_fullsock(r6); > r0 = sk_fullsock(r6); > if (r0 == 0) return 0; (a) > if (r0 != r7) return 0; (b) > *r7->type; (c) > return 0; > > It is safe to dereference r7 at point (c), because of (a) and (b). I think the idea makes sense. Perhaps Yonhong can comment seeing he was active on the LLVM thread. I just scanned the LLVM side for now will take a look in more detail in a bit. > > Signed-off-by: Eduard Zingerman <eddyz87@gmail.com> > --- > kernel/bpf/verifier.c | 39 ++++++++++++++++++++++++++++++++++++++- > 1 file changed, 38 insertions(+), 1 deletion(-) > > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > index 2c1f8069f7b7..c48d34625bfd 100644 > --- a/kernel/bpf/verifier.c > +++ b/kernel/bpf/verifier.c > @@ -472,6 +472,11 @@ static bool type_may_be_null(u32 type) > return type & PTR_MAYBE_NULL; > } > > +static bool type_is_pointer(enum bpf_reg_type type) > +{ > + return type != NOT_INIT && type != SCALAR_VALUE; > +} > + Instead of having another helper is_pointer_value() could work here? Checking if we need NOT_INIT in that helper now. > static bool is_acquire_function(enum bpf_func_id func_id, > const struct bpf_map *map) > { > @@ -10046,6 +10051,7 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env, > struct bpf_verifier_state *other_branch; > struct bpf_reg_state *regs = this_branch->frame[this_branch->curframe]->regs; > struct bpf_reg_state *dst_reg, *other_branch_regs, *src_reg = NULL; > + struct bpf_reg_state *eq_branch_regs; > u8 opcode = BPF_OP(insn->code); > bool is_jmp32; > int pred = -1; > @@ -10155,7 +10161,7 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env, > /* detect if we are comparing against a constant value so we can adjust > * our min/max values for our dst register. > * this is only legit if both are scalars (or pointers to the same > - * object, I suppose, but we don't support that right now), because > + * object, I suppose, see the next if block), because > * otherwise the different base pointers mean the offsets aren't > * comparable. > */ > @@ -10199,6 +10205,37 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env, > opcode, is_jmp32); > } > > + /* if one pointer register is compared to another pointer > + * register check if PTR_MAYBE_NULL could be lifted. > + * E.g. register A - maybe null > + * register B - not null > + * for JNE A, B, ... - A is not null in the false branch; > + * for JEQ A, B, ... - A is not null in the true branch. > + */ > + if (!is_jmp32 && > + BPF_SRC(insn->code) == BPF_X && > + type_is_pointer(src_reg->type) && type_is_pointer(dst_reg->type) && > + type_may_be_null(src_reg->type) != type_may_be_null(dst_reg->type)) { > + eq_branch_regs = NULL; > + switch (opcode) { > + case BPF_JEQ: > + eq_branch_regs = other_branch_regs; > + break; > + case BPF_JNE: > + eq_branch_regs = regs; > + break; > + default: > + /* do nothing */ > + break; > + } > + if (eq_branch_regs) { > + if (type_may_be_null(src_reg->type)) > + mark_ptr_not_null_reg(&eq_branch_regs[insn->src_reg]); > + else > + mark_ptr_not_null_reg(&eq_branch_regs[insn->dst_reg]); > + } > + } > + > if (dst_reg->type == SCALAR_VALUE && dst_reg->id && > !WARN_ON_ONCE(dst_reg->id != other_branch_regs[insn->dst_reg].id)) { > find_equal_scalars(this_branch, dst_reg); > -- > 2.37.1 > ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH RFC bpf-next 1/2] bpf: propagate nullness information for reg to reg comparisons 2022-08-23 23:15 ` John Fastabend @ 2022-08-24 22:05 ` Eduard Zingerman 2022-08-25 6:21 ` John Fastabend 2022-08-25 2:55 ` Yonghong Song 1 sibling, 1 reply; 11+ messages in thread From: Eduard Zingerman @ 2022-08-24 22:05 UTC (permalink / raw) To: John Fastabend, bpf, ast, andrii, daniel, kernel-team, yhs > On Tue, 2022-08-23 at 16:15 -0700, John Fastabend wrote: Hi John, Thank you for commenting! > > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > > index 2c1f8069f7b7..c48d34625bfd 100644 > > --- a/kernel/bpf/verifier.c > > +++ b/kernel/bpf/verifier.c > > @@ -472,6 +472,11 @@ static bool type_may_be_null(u32 type) > > return type & PTR_MAYBE_NULL; > > } > > > > +static bool type_is_pointer(enum bpf_reg_type type) > > +{ > > + return type != NOT_INIT && type != SCALAR_VALUE; > > +} > > + > > Instead of having another helper is_pointer_value() could work here? > Checking if we need NOT_INIT in that helper now. Do you mean modifying the `__is_pointer_value` by adding a check `reg->type != NOT_INIT`? I tried this out and tests are passing, but __is_pointer_value / is_pointer_value are used in a lot of places, seem to be a scary change, to be honest. Thanks, Eduard ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH RFC bpf-next 1/2] bpf: propagate nullness information for reg to reg comparisons 2022-08-24 22:05 ` Eduard Zingerman @ 2022-08-25 6:21 ` John Fastabend 2022-08-25 22:31 ` Eduard Zingerman 0 siblings, 1 reply; 11+ messages in thread From: John Fastabend @ 2022-08-25 6:21 UTC (permalink / raw) To: Eduard Zingerman, John Fastabend, bpf, ast, andrii, daniel, kernel-team, yhs Eduard Zingerman wrote: > > On Tue, 2022-08-23 at 16:15 -0700, John Fastabend wrote: > > Hi John, > > Thank you for commenting! > > > > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > > > index 2c1f8069f7b7..c48d34625bfd 100644 > > > --- a/kernel/bpf/verifier.c > > > +++ b/kernel/bpf/verifier.c > > > @@ -472,6 +472,11 @@ static bool type_may_be_null(u32 type) > > > return type & PTR_MAYBE_NULL; > > > } > > > > > > +static bool type_is_pointer(enum bpf_reg_type type) > > > +{ > > > + return type != NOT_INIT && type != SCALAR_VALUE; > > > +} > > > + > > > > Instead of having another helper is_pointer_value() could work here? > > Checking if we need NOT_INIT in that helper now. > > Do you mean modifying the `__is_pointer_value` by adding a check > `reg->type != NOT_INIT`? > > I tried this out and tests are passing, but __is_pointer_value / > is_pointer_value are used in a lot of places, seem to be a scary > change, to be honest. Agree it looks scary I wanted to play around with it more. I agree its not the same and off to investigate a few places we use __is_pointer_value now. Might add a few more tests while I'm at it. > > Thanks, > Eduard ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH RFC bpf-next 1/2] bpf: propagate nullness information for reg to reg comparisons 2022-08-25 6:21 ` John Fastabend @ 2022-08-25 22:31 ` Eduard Zingerman 0 siblings, 0 replies; 11+ messages in thread From: Eduard Zingerman @ 2022-08-25 22:31 UTC (permalink / raw) To: John Fastabend, bpf, ast, andrii, daniel, kernel-team, yhs Hi John, > Agree it looks scary I wanted to play around with it more. I agree > its not the same and off to investigate a few places we use > __is_pointer_value now. Might add a few more tests while I'm at it. I think that update to `__is_pointer_value` should probably be done but is unrelated to this patch. And, as you mention, would require crafting some number of test cases for NOT_INIT case :) I'd prefer to keep the current predicate as is and reuse it at some later point in the updated `__is_pointer_value` function. What do you think? Thanks, Eduard ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH RFC bpf-next 1/2] bpf: propagate nullness information for reg to reg comparisons 2022-08-23 23:15 ` John Fastabend 2022-08-24 22:05 ` Eduard Zingerman @ 2022-08-25 2:55 ` Yonghong Song 2022-08-25 6:19 ` John Fastabend 1 sibling, 1 reply; 11+ messages in thread From: Yonghong Song @ 2022-08-25 2:55 UTC (permalink / raw) To: John Fastabend, Eduard Zingerman, bpf, ast, andrii, daniel, kernel-team On 8/23/22 4:15 PM, John Fastabend wrote: > Eduard Zingerman wrote: >> Propagate nullness information for branches of register to register >> equality compare instructions. The following rules are used: >> - suppose register A maybe null >> - suppose register B is not null >> - for JNE A, B, ... - A is not null in the false branch >> - for JEQ A, B, ... - A is not null in the true branch >> >> E.g. for program like below: >> >> r6 = skb->sk; >> r7 = sk_fullsock(r6); >> r0 = sk_fullsock(r6); >> if (r0 == 0) return 0; (a) >> if (r0 != r7) return 0; (b) >> *r7->type; (c) >> return 0; >> >> It is safe to dereference r7 at point (c), because of (a) and (b). > > I think the idea makes sense. Perhaps Yonhong can comment seeing he was active > on the LLVM thread. I just scanned the LLVM side for now will take a look > in more detail in a bit. The issue is discovered when making some changes in llvm compiler. I think it is good to add support in verifier so in the future if compiler generates such code patterns, user won't get surprised verification failure. > >> >> Signed-off-by: Eduard Zingerman <eddyz87@gmail.com> >> --- >> kernel/bpf/verifier.c | 39 ++++++++++++++++++++++++++++++++++++++- >> 1 file changed, 38 insertions(+), 1 deletion(-) >> >> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c >> index 2c1f8069f7b7..c48d34625bfd 100644 >> --- a/kernel/bpf/verifier.c >> +++ b/kernel/bpf/verifier.c >> @@ -472,6 +472,11 @@ static bool type_may_be_null(u32 type) >> return type & PTR_MAYBE_NULL; >> } >> >> +static bool type_is_pointer(enum bpf_reg_type type) >> +{ >> + return type != NOT_INIT && type != SCALAR_VALUE; >> +} >> + > > Instead of having another helper is_pointer_value() could work here? > Checking if we need NOT_INIT in that helper now. > >> static bool is_acquire_function(enum bpf_func_id func_id, >> const struct bpf_map *map) >> { >> @@ -10046,6 +10051,7 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env, >> struct bpf_verifier_state *other_branch; >> struct bpf_reg_state *regs = this_branch->frame[this_branch->curframe]->regs; >> struct bpf_reg_state *dst_reg, *other_branch_regs, *src_reg = NULL; >> + struct bpf_reg_state *eq_branch_regs; >> u8 opcode = BPF_OP(insn->code); >> bool is_jmp32; >> int pred = -1; >> @@ -10155,7 +10161,7 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env, >> /* detect if we are comparing against a constant value so we can adjust >> * our min/max values for our dst register. >> * this is only legit if both are scalars (or pointers to the same >> - * object, I suppose, but we don't support that right now), because >> + * object, I suppose, see the next if block), because >> * otherwise the different base pointers mean the offsets aren't >> * comparable. >> */ >> @@ -10199,6 +10205,37 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env, >> opcode, is_jmp32); >> } >> >> + /* if one pointer register is compared to another pointer >> + * register check if PTR_MAYBE_NULL could be lifted. >> + * E.g. register A - maybe null >> + * register B - not null >> + * for JNE A, B, ... - A is not null in the false branch; >> + * for JEQ A, B, ... - A is not null in the true branch. >> + */ >> + if (!is_jmp32 && >> + BPF_SRC(insn->code) == BPF_X && >> + type_is_pointer(src_reg->type) && type_is_pointer(dst_reg->type) && >> + type_may_be_null(src_reg->type) != type_may_be_null(dst_reg->type)) { >> + eq_branch_regs = NULL; >> + switch (opcode) { >> + case BPF_JEQ: >> + eq_branch_regs = other_branch_regs; >> + break; >> + case BPF_JNE: >> + eq_branch_regs = regs; >> + break; >> + default: >> + /* do nothing */ >> + break; >> + } >> + if (eq_branch_regs) { >> + if (type_may_be_null(src_reg->type)) >> + mark_ptr_not_null_reg(&eq_branch_regs[insn->src_reg]); >> + else >> + mark_ptr_not_null_reg(&eq_branch_regs[insn->dst_reg]); >> + } >> + } >> + >> if (dst_reg->type == SCALAR_VALUE && dst_reg->id && >> !WARN_ON_ONCE(dst_reg->id != other_branch_regs[insn->dst_reg].id)) { >> find_equal_scalars(this_branch, dst_reg); >> -- >> 2.37.1 >> > > ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH RFC bpf-next 1/2] bpf: propagate nullness information for reg to reg comparisons 2022-08-25 2:55 ` Yonghong Song @ 2022-08-25 6:19 ` John Fastabend 0 siblings, 0 replies; 11+ messages in thread From: John Fastabend @ 2022-08-25 6:19 UTC (permalink / raw) To: Yonghong Song, John Fastabend, Eduard Zingerman, bpf, ast, andrii, daniel, kernel-team Yonghong Song wrote: > > > On 8/23/22 4:15 PM, John Fastabend wrote: > > Eduard Zingerman wrote: > >> Propagate nullness information for branches of register to register > >> equality compare instructions. The following rules are used: > >> - suppose register A maybe null > >> - suppose register B is not null > >> - for JNE A, B, ... - A is not null in the false branch > >> - for JEQ A, B, ... - A is not null in the true branch > >> > >> E.g. for program like below: > >> > >> r6 = skb->sk; > >> r7 = sk_fullsock(r6); > >> r0 = sk_fullsock(r6); > >> if (r0 == 0) return 0; (a) > >> if (r0 != r7) return 0; (b) > >> *r7->type; (c) > >> return 0; > >> > >> It is safe to dereference r7 at point (c), because of (a) and (b). > > > > I think the idea makes sense. Perhaps Yonhong can comment seeing he was active > > on the LLVM thread. I just scanned the LLVM side for now will take a look > > in more detail in a bit. > > The issue is discovered when making some changes in llvm compiler. > I think it is good to add support in verifier so in the future > if compiler generates such code patterns, user won't get > surprised verification failure. > I agree. Read the LLVM thread as well. ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH RFC bpf-next 1/2] bpf: propagate nullness information for reg to reg comparisons 2022-08-22 9:43 ` [PATCH RFC bpf-next 1/2] bpf: " Eduard Zingerman 2022-08-23 23:15 ` John Fastabend @ 2022-08-25 2:34 ` Yonghong Song 1 sibling, 0 replies; 11+ messages in thread From: Yonghong Song @ 2022-08-25 2:34 UTC (permalink / raw) To: Eduard Zingerman, bpf, ast, andrii, daniel, kernel-team On 8/22/22 2:43 AM, Eduard Zingerman wrote: > Propagate nullness information for branches of register to register > equality compare instructions. The following rules are used: > - suppose register A maybe null > - suppose register B is not null > - for JNE A, B, ... - A is not null in the false branch > - for JEQ A, B, ... - A is not null in the true branch > > E.g. for program like below: > > r6 = skb->sk; > r7 = sk_fullsock(r6); > r0 = sk_fullsock(r6); > if (r0 == 0) return 0; (a) > if (r0 != r7) return 0; (b) > *r7->type; (c) > return 0; > > It is safe to dereference r7 at point (c), because of (a) and (b). > > Signed-off-by: Eduard Zingerman <eddyz87@gmail.com> You can remove RFC tag in the next revision. LGTM with one nit below. Acked-by: Yonghong Song <yhs@fb.com> > --- > kernel/bpf/verifier.c | 39 ++++++++++++++++++++++++++++++++++++++- > 1 file changed, 38 insertions(+), 1 deletion(-) > > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > index 2c1f8069f7b7..c48d34625bfd 100644 > --- a/kernel/bpf/verifier.c > +++ b/kernel/bpf/verifier.c > @@ -472,6 +472,11 @@ static bool type_may_be_null(u32 type) > return type & PTR_MAYBE_NULL; > } > > +static bool type_is_pointer(enum bpf_reg_type type) > +{ > + return type != NOT_INIT && type != SCALAR_VALUE; > +} > + > static bool is_acquire_function(enum bpf_func_id func_id, > const struct bpf_map *map) > { > @@ -10046,6 +10051,7 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env, > struct bpf_verifier_state *other_branch; > struct bpf_reg_state *regs = this_branch->frame[this_branch->curframe]->regs; > struct bpf_reg_state *dst_reg, *other_branch_regs, *src_reg = NULL; > + struct bpf_reg_state *eq_branch_regs; > u8 opcode = BPF_OP(insn->code); > bool is_jmp32; > int pred = -1; > @@ -10155,7 +10161,7 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env, > /* detect if we are comparing against a constant value so we can adjust > * our min/max values for our dst register. > * this is only legit if both are scalars (or pointers to the same > - * object, I suppose, but we don't support that right now), because > + * object, I suppose, see the next if block), because > * otherwise the different base pointers mean the offsets aren't > * comparable. > */ > @@ -10199,6 +10205,37 @@ static int check_cond_jmp_op(struct bpf_verifier_env *env, > opcode, is_jmp32); > } > > + /* if one pointer register is compared to another pointer > + * register check if PTR_MAYBE_NULL could be lifted. > + * E.g. register A - maybe null > + * register B - not null > + * for JNE A, B, ... - A is not null in the false branch; > + * for JEQ A, B, ... - A is not null in the true branch. > + */ > + if (!is_jmp32 && > + BPF_SRC(insn->code) == BPF_X && > + type_is_pointer(src_reg->type) && type_is_pointer(dst_reg->type) && > + type_may_be_null(src_reg->type) != type_may_be_null(dst_reg->type)) { > + eq_branch_regs = NULL; > + switch (opcode) { > + case BPF_JEQ: > + eq_branch_regs = other_branch_regs; > + break; > + case BPF_JNE: > + eq_branch_regs = regs; > + break; > + default: > + /* do nothing */ > + break; > + } > + if (eq_branch_regs) { > + if (type_may_be_null(src_reg->type)) > + mark_ptr_not_null_reg(&eq_branch_regs[insn->src_reg]); > + else > + mark_ptr_not_null_reg(&eq_branch_regs[insn->dst_reg]); > + } > + } I suggest put the above code after below if condition so the new code can be closer to other codes with make_ptr_not_null_reg. > + > if (dst_reg->type == SCALAR_VALUE && dst_reg->id && > !WARN_ON_ONCE(dst_reg->id != other_branch_regs[insn->dst_reg].id)) { > find_equal_scalars(this_branch, dst_reg); ^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH RFC bpf-next 2/2] selftests/bpf: check nullness propagation for reg to reg comparisons 2022-08-22 9:43 [PATCH RFC bpf-next 0/2] propagate nullness information for reg to reg comparisons Eduard Zingerman 2022-08-22 9:43 ` [PATCH RFC bpf-next 1/2] bpf: " Eduard Zingerman @ 2022-08-22 9:43 ` Eduard Zingerman 2022-08-25 2:38 ` Yonghong Song 1 sibling, 1 reply; 11+ messages in thread From: Eduard Zingerman @ 2022-08-22 9:43 UTC (permalink / raw) To: bpf, ast, andrii, daniel, kernel-team, yhs; +Cc: Eduard Zingerman Verify that nullness information is porpagated in the branches of register to register JEQ and JNE operations. Signed-off-by: Eduard Zingerman <eddyz87@gmail.com> --- .../bpf/verifier/jeq_infer_not_null.c | 186 ++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 tools/testing/selftests/bpf/verifier/jeq_infer_not_null.c diff --git a/tools/testing/selftests/bpf/verifier/jeq_infer_not_null.c b/tools/testing/selftests/bpf/verifier/jeq_infer_not_null.c new file mode 100644 index 000000000000..1af9fdd31f00 --- /dev/null +++ b/tools/testing/selftests/bpf/verifier/jeq_infer_not_null.c @@ -0,0 +1,186 @@ +{ + /* This is equivalent to the following program: + * + * r6 = skb->sk; + * r7 = sk_fullsock(r6); + * r0 = sk_fullsock(r6); + * if (r0 == 0) return 0; (a) + * if (r0 != r7) return 0; (b) + * *r7->type; (c) + * return 0; + * + * It is safe to dereference r7 at point (c), because of (a) and (b). + * The test verifies that relation r0 == r7 is propagated from (b) to (c). + */ + "jne/jeq infer not null, PTR_TO_SOCKET_OR_NULL -> PTR_TO_SOCKET for JNE false branch", + .insns = { + /* r6 = skb->sk; */ + BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_1, offsetof(struct __sk_buff, sk)), + /* if (r6 == 0) return 0; */ + BPF_JMP_IMM(BPF_JNE, BPF_REG_6, 0, 2), + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + /* r7 = sk_fullsock(skb); */ + BPF_MOV64_REG(BPF_REG_1, BPF_REG_6), + BPF_EMIT_CALL(BPF_FUNC_sk_fullsock), + BPF_MOV64_REG(BPF_REG_7, BPF_REG_0), + /* r0 = sk_fullsock(skb); */ + BPF_MOV64_REG(BPF_REG_1, BPF_REG_6), + BPF_EMIT_CALL(BPF_FUNC_sk_fullsock), + /* if (r0 == null) return 0; */ + BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 2), + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + /* if (r0 == r7) r0 = *(r7->type); */ + BPF_JMP_REG(BPF_JNE, BPF_REG_0, BPF_REG_7, 1), /* Use ! JNE ! */ + BPF_LDX_MEM(BPF_W, BPF_REG_0, BPF_REG_7, offsetof(struct bpf_sock, type)), + /* return 0 */ + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + }, + .prog_type = BPF_PROG_TYPE_CGROUP_SKB, + .result = ACCEPT, +}, +{ + /* Same as above, but verify that another branch of JNE still + * prohibits access to PTR_MAYBE_NULL. + */ + "jne/jeq infer not null, PTR_TO_SOCKET_OR_NULL unchanged for JNE true branch", + .insns = { + /* r6 = skb->sk */ + BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_1, offsetof(struct __sk_buff, sk)), + /* if (r6 == 0) return 0; */ + BPF_JMP_IMM(BPF_JNE, BPF_REG_6, 0, 2), + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + /* r7 = sk_fullsock(skb); */ + BPF_MOV64_REG(BPF_REG_1, BPF_REG_6), + BPF_EMIT_CALL(BPF_FUNC_sk_fullsock), + BPF_MOV64_REG(BPF_REG_7, BPF_REG_0), + /* r0 = sk_fullsock(skb); */ + BPF_MOV64_REG(BPF_REG_1, BPF_REG_6), + BPF_EMIT_CALL(BPF_FUNC_sk_fullsock), + /* if (r0 == null) return 0; */ + BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 2), + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + /* if (r0 == r7) return 0; */ + BPF_JMP_REG(BPF_JNE, BPF_REG_0, BPF_REG_7, 2), /* Use ! JNE ! */ + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + /* r0 = *(r7->type); */ + BPF_LDX_MEM(BPF_W, BPF_REG_0, BPF_REG_7, offsetof(struct bpf_sock, type)), + /* return 0 */ + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + }, + .prog_type = BPF_PROG_TYPE_CGROUP_SKB, + .result = REJECT, + .errstr = "R7 invalid mem access 'sock_or_null'", +}, +{ + /* Same as a first test, but not null should be inferred for JEQ branch */ + "jne/jeq infer not null, PTR_TO_SOCKET_OR_NULL -> PTR_TO_SOCKET for JEQ true branch", + .insns = { + /* r6 = skb->sk; */ + BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_1, offsetof(struct __sk_buff, sk)), + /* if (r6 == null) return 0; */ + BPF_JMP_IMM(BPF_JNE, BPF_REG_6, 0, 2), + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + /* r7 = sk_fullsock(skb); */ + BPF_MOV64_REG(BPF_REG_1, BPF_REG_6), + BPF_EMIT_CALL(BPF_FUNC_sk_fullsock), + BPF_MOV64_REG(BPF_REG_7, BPF_REG_0), + /* r0 = sk_fullsock(skb); */ + BPF_MOV64_REG(BPF_REG_1, BPF_REG_6), + BPF_EMIT_CALL(BPF_FUNC_sk_fullsock), + /* if (r0 == null) return 0; */ + BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 2), + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + /* if (r0 != r7) return 0; */ + BPF_JMP_REG(BPF_JEQ, BPF_REG_0, BPF_REG_7, 2), /* Use ! JEQ ! */ + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + /* r0 = *(r7->type); */ + BPF_LDX_MEM(BPF_W, BPF_REG_0, BPF_REG_7, offsetof(struct bpf_sock, type)), + /* return 0; */ + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + }, + .prog_type = BPF_PROG_TYPE_CGROUP_SKB, + .result = ACCEPT, +}, +{ + /* Same as above, but verify that another branch of JNE still + * prohibits access to PTR_MAYBE_NULL. + */ + "jne/jeq infer not null, PTR_TO_SOCKET_OR_NULL unchanged for JEQ false branch", + .insns = { + /* r6 = skb->sk; */ + BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_1, offsetof(struct __sk_buff, sk)), + /* if (r6 == null) return 0; */ + BPF_JMP_IMM(BPF_JNE, BPF_REG_6, 0, 2), + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + /* r7 = sk_fullsock(skb); */ + BPF_MOV64_REG(BPF_REG_1, BPF_REG_6), + BPF_EMIT_CALL(BPF_FUNC_sk_fullsock), + BPF_MOV64_REG(BPF_REG_7, BPF_REG_0), + /* r0 = sk_fullsock(skb); */ + BPF_MOV64_REG(BPF_REG_1, BPF_REG_6), + BPF_EMIT_CALL(BPF_FUNC_sk_fullsock), + /* if (r0 == null) return 0; */ + BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 2), + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + /* if (r0 != r7) r0 = *(r7->type); */ + BPF_JMP_REG(BPF_JEQ, BPF_REG_0, BPF_REG_7, 1), /* Use ! JEQ ! */ + BPF_LDX_MEM(BPF_W, BPF_REG_0, BPF_REG_7, offsetof(struct bpf_sock, type)), + /* return 0; */ + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + }, + .prog_type = BPF_PROG_TYPE_CGROUP_SKB, + .result = REJECT, + .errstr = "R7 invalid mem access 'sock_or_null'", +}, +{ + /* Maps are treated in a different branch of `mark_ptr_not_null_reg`, + * so separate test for maps case. + */ + "jne/jeq infer not null, PTR_TO_MAP_VALUE_OR_NULL -> PTR_TO_MAP_VALUE", + .insns = { + /* r9 = &some stack to use as key */ + BPF_ST_MEM(BPF_W, BPF_REG_10, -8, 0), + BPF_MOV64_REG(BPF_REG_9, BPF_REG_10), + BPF_ALU64_IMM(BPF_ADD, BPF_REG_9, -8), + /* r8 = process local map */ + BPF_LD_MAP_FD(BPF_REG_8, 0), + /* r6 = map_lookup_elem(r8, r9); */ + BPF_MOV64_REG(BPF_REG_1, BPF_REG_8), + BPF_MOV64_REG(BPF_REG_2, BPF_REG_9), + BPF_EMIT_CALL(BPF_FUNC_map_lookup_elem), + BPF_MOV64_REG(BPF_REG_6, BPF_REG_0), + /* r7 = map_lookup_elem(r8, r9); */ + BPF_MOV64_REG(BPF_REG_1, BPF_REG_8), + BPF_MOV64_REG(BPF_REG_2, BPF_REG_9), + BPF_EMIT_CALL(BPF_FUNC_map_lookup_elem), + BPF_MOV64_REG(BPF_REG_7, BPF_REG_0), + /* if (r6 == 0) return 0; */ + BPF_JMP_IMM(BPF_JNE, BPF_REG_6, 0, 2), + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + /* if (r6 != r7) return 0; */ + BPF_JMP_REG(BPF_JNE, BPF_REG_6, BPF_REG_7, 1), + /* read *r7; */ + BPF_LDX_MEM(BPF_W, BPF_REG_0, BPF_REG_7, offsetof(struct bpf_xdp_sock, queue_id)), + /* return 0; */ + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + }, + .fixup_map_xskmap = { 3 }, + .prog_type = BPF_PROG_TYPE_XDP, + .result = ACCEPT, +}, -- 2.37.1 ^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH RFC bpf-next 2/2] selftests/bpf: check nullness propagation for reg to reg comparisons 2022-08-22 9:43 ` [PATCH RFC bpf-next 2/2] selftests/bpf: check nullness propagation " Eduard Zingerman @ 2022-08-25 2:38 ` Yonghong Song 0 siblings, 0 replies; 11+ messages in thread From: Yonghong Song @ 2022-08-25 2:38 UTC (permalink / raw) To: Eduard Zingerman, bpf, ast, andrii, daniel, kernel-team On 8/22/22 2:43 AM, Eduard Zingerman wrote: > Verify that nullness information is porpagated in the branches of > register to register JEQ and JNE operations. > > Signed-off-by: Eduard Zingerman <eddyz87@gmail.com> LGTM with one nit below. Acked-by: Yonghong Song <yhs@fb.com> > --- > .../bpf/verifier/jeq_infer_not_null.c | 186 ++++++++++++++++++ > 1 file changed, 186 insertions(+) > create mode 100644 tools/testing/selftests/bpf/verifier/jeq_infer_not_null.c > > diff --git a/tools/testing/selftests/bpf/verifier/jeq_infer_not_null.c b/tools/testing/selftests/bpf/verifier/jeq_infer_not_null.c > new file mode 100644 > index 000000000000..1af9fdd31f00 > --- /dev/null > +++ b/tools/testing/selftests/bpf/verifier/jeq_infer_not_null.c > @@ -0,0 +1,186 @@ > +{ > + /* This is equivalent to the following program: > + * > + * r6 = skb->sk; > + * r7 = sk_fullsock(r6); > + * r0 = sk_fullsock(r6); > + * if (r0 == 0) return 0; (a) > + * if (r0 != r7) return 0; (b) > + * *r7->type; (c) > + * return 0; > + * > + * It is safe to dereference r7 at point (c), because of (a) and (b). > + * The test verifies that relation r0 == r7 is propagated from (b) to (c). > + */ > + "jne/jeq infer not null, PTR_TO_SOCKET_OR_NULL -> PTR_TO_SOCKET for JNE false branch", > + .insns = { > + /* r6 = skb->sk; */ > + BPF_LDX_MEM(BPF_DW, BPF_REG_6, BPF_REG_1, offsetof(struct __sk_buff, sk)), > + /* if (r6 == 0) return 0; */ > + BPF_JMP_IMM(BPF_JNE, BPF_REG_6, 0, 2), > + BPF_MOV64_IMM(BPF_REG_0, 0), > + BPF_EXIT_INSN(), In general, for each test case, we only have one BPF_MOV64_IMM(BPF_REG_0, 0), BPF_EXIT_INSN() to simulate the C generated code. It would be good if we keep this way. The same for other test cases. > + /* r7 = sk_fullsock(skb); */ > + BPF_MOV64_REG(BPF_REG_1, BPF_REG_6), > + BPF_EMIT_CALL(BPF_FUNC_sk_fullsock), > + BPF_MOV64_REG(BPF_REG_7, BPF_REG_0), > + /* r0 = sk_fullsock(skb); */ > + BPF_MOV64_REG(BPF_REG_1, BPF_REG_6), > + BPF_EMIT_CALL(BPF_FUNC_sk_fullsock), > + /* if (r0 == null) return 0; */ > + BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 2), > + BPF_MOV64_IMM(BPF_REG_0, 0), > + BPF_EXIT_INSN(), > + /* if (r0 == r7) r0 = *(r7->type); */ > + BPF_JMP_REG(BPF_JNE, BPF_REG_0, BPF_REG_7, 1), /* Use ! JNE ! */ > + BPF_LDX_MEM(BPF_W, BPF_REG_0, BPF_REG_7, offsetof(struct bpf_sock, type)), > + /* return 0 */ > + BPF_MOV64_IMM(BPF_REG_0, 0), > + BPF_EXIT_INSN(), > + }, > + .prog_type = BPF_PROG_TYPE_CGROUP_SKB, > + .result = ACCEPT, > +}, [...] ^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2022-08-25 22:31 UTC | newest] Thread overview: 11+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2022-08-22 9:43 [PATCH RFC bpf-next 0/2] propagate nullness information for reg to reg comparisons Eduard Zingerman 2022-08-22 9:43 ` [PATCH RFC bpf-next 1/2] bpf: " Eduard Zingerman 2022-08-23 23:15 ` John Fastabend 2022-08-24 22:05 ` Eduard Zingerman 2022-08-25 6:21 ` John Fastabend 2022-08-25 22:31 ` Eduard Zingerman 2022-08-25 2:55 ` Yonghong Song 2022-08-25 6:19 ` John Fastabend 2022-08-25 2:34 ` Yonghong Song 2022-08-22 9:43 ` [PATCH RFC bpf-next 2/2] selftests/bpf: check nullness propagation " Eduard Zingerman 2022-08-25 2:38 ` Yonghong Song
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox