* [PATCH bpf-next 0/3] infer packet range for 'if pkt ==/!= pkt_end' instructions
@ 2024-01-08 13:27 Eduard Zingerman
2024-01-08 13:28 ` [PATCH bpf-next 1/3] bpf: simplify try_match_pkt_pointers() Eduard Zingerman
` (2 more replies)
0 siblings, 3 replies; 16+ messages in thread
From: Eduard Zingerman @ 2024-01-08 13:27 UTC (permalink / raw)
To: bpf, ast
Cc: andrii, daniel, martin.lau, kernel-team, yonghong.song,
zenczykowski, Eduard Zingerman
As suggested by Maciej Żenczykowski in [1], extend try_match_pkt_pointers
to allow verification of BPF, generated for C code like below:
if (data + 42 != data_end) { ... }
Also simplify try_match_pkt_pointers to avoid checking both
'pkt <op> pkt_end' and 'pkt_end <op> pkt' conditions,
as suggested by Andrii Nakryiko.
[1] https://lore.kernel.org/bpf/CAHo-Oow5V2u4ZYvzuR8NmJmFDPNYp0pQDJX66rZqUjFHvhx82A@mail.gmail.com/
Eduard Zingerman (3):
bpf: simplify try_match_pkt_pointers()
bpf: infer packet range for 'if pkt ==/!= pkt_end' comparisons
selftests/bpf: test packet range inference for 'if pkt ==/!= pkt_end'
kernel/bpf/verifier.c | 112 ++++----------
.../bpf/progs/verifier_direct_packet_access.c | 138 ++++++++++++++++++
2 files changed, 170 insertions(+), 80 deletions(-)
--
2.43.0
^ permalink raw reply [flat|nested] 16+ messages in thread* [PATCH bpf-next 1/3] bpf: simplify try_match_pkt_pointers() 2024-01-08 13:27 [PATCH bpf-next 0/3] infer packet range for 'if pkt ==/!= pkt_end' instructions Eduard Zingerman @ 2024-01-08 13:28 ` Eduard Zingerman 2024-01-09 0:40 ` Andrii Nakryiko 2024-01-08 13:28 ` [PATCH bpf-next 2/3] bpf: infer packet range for 'if pkt ==/!= pkt_end' comparisons Eduard Zingerman 2024-01-08 13:28 ` [PATCH bpf-next 3/3] selftests/bpf: test packet range inference for 'if pkt ==/!= pkt_end' Eduard Zingerman 2 siblings, 1 reply; 16+ messages in thread From: Eduard Zingerman @ 2024-01-08 13:28 UTC (permalink / raw) To: bpf, ast Cc: andrii, daniel, martin.lau, kernel-team, yonghong.song, zenczykowski, Eduard Zingerman, Andrii Nakryiko Reduce number of cases handled in try_match_pkt_pointers() to <pkt_data> <op> <pkt_end> or <pkt_meta> <op> <pkt_data> by flipping opcode. Suggested-by: Andrii Nakryiko <andrii.nakryiko@gmail.com> Signed-off-by: Eduard Zingerman <eddyz87@gmail.com> --- kernel/bpf/verifier.c | 104 ++++++++++-------------------------------- 1 file changed, 24 insertions(+), 80 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index adbf330d364b..918e6a7912e2 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -14677,6 +14677,9 @@ static bool try_match_pkt_pointers(const struct bpf_insn *insn, struct bpf_verifier_state *this_branch, struct bpf_verifier_state *other_branch) { + int opcode = BPF_OP(insn->code); + int dst_regno = insn->dst_reg; + if (BPF_SRC(insn->code) != BPF_X) return false; @@ -14684,90 +14687,31 @@ static bool try_match_pkt_pointers(const struct bpf_insn *insn, if (BPF_CLASS(insn->code) == BPF_JMP32) return false; - switch (BPF_OP(insn->code)) { + if (dst_reg->type == PTR_TO_PACKET_END || + src_reg->type == PTR_TO_PACKET_META) { + swap(src_reg, dst_reg); + dst_regno = insn->src_reg; + opcode = flip_opcode(opcode); + } + + if ((dst_reg->type != PTR_TO_PACKET || + src_reg->type != PTR_TO_PACKET_END) && + (dst_reg->type != PTR_TO_PACKET_META || + !reg_is_init_pkt_pointer(src_reg, PTR_TO_PACKET))) + return false; + + switch (opcode) { case BPF_JGT: - if ((dst_reg->type == PTR_TO_PACKET && - src_reg->type == PTR_TO_PACKET_END) || - (dst_reg->type == PTR_TO_PACKET_META && - reg_is_init_pkt_pointer(src_reg, PTR_TO_PACKET))) { - /* pkt_data' > pkt_end, pkt_meta' > pkt_data */ - find_good_pkt_pointers(this_branch, dst_reg, - dst_reg->type, false); - mark_pkt_end(other_branch, insn->dst_reg, true); - } else if ((dst_reg->type == PTR_TO_PACKET_END && - src_reg->type == PTR_TO_PACKET) || - (reg_is_init_pkt_pointer(dst_reg, PTR_TO_PACKET) && - src_reg->type == PTR_TO_PACKET_META)) { - /* pkt_end > pkt_data', pkt_data > pkt_meta' */ - find_good_pkt_pointers(other_branch, src_reg, - src_reg->type, true); - mark_pkt_end(this_branch, insn->src_reg, false); - } else { - return false; - } - break; - case BPF_JLT: - if ((dst_reg->type == PTR_TO_PACKET && - src_reg->type == PTR_TO_PACKET_END) || - (dst_reg->type == PTR_TO_PACKET_META && - reg_is_init_pkt_pointer(src_reg, PTR_TO_PACKET))) { - /* pkt_data' < pkt_end, pkt_meta' < pkt_data */ - find_good_pkt_pointers(other_branch, dst_reg, - dst_reg->type, true); - mark_pkt_end(this_branch, insn->dst_reg, false); - } else if ((dst_reg->type == PTR_TO_PACKET_END && - src_reg->type == PTR_TO_PACKET) || - (reg_is_init_pkt_pointer(dst_reg, PTR_TO_PACKET) && - src_reg->type == PTR_TO_PACKET_META)) { - /* pkt_end < pkt_data', pkt_data > pkt_meta' */ - find_good_pkt_pointers(this_branch, src_reg, - src_reg->type, false); - mark_pkt_end(other_branch, insn->src_reg, true); - } else { - return false; - } - break; case BPF_JGE: - if ((dst_reg->type == PTR_TO_PACKET && - src_reg->type == PTR_TO_PACKET_END) || - (dst_reg->type == PTR_TO_PACKET_META && - reg_is_init_pkt_pointer(src_reg, PTR_TO_PACKET))) { - /* pkt_data' >= pkt_end, pkt_meta' >= pkt_data */ - find_good_pkt_pointers(this_branch, dst_reg, - dst_reg->type, true); - mark_pkt_end(other_branch, insn->dst_reg, false); - } else if ((dst_reg->type == PTR_TO_PACKET_END && - src_reg->type == PTR_TO_PACKET) || - (reg_is_init_pkt_pointer(dst_reg, PTR_TO_PACKET) && - src_reg->type == PTR_TO_PACKET_META)) { - /* pkt_end >= pkt_data', pkt_data >= pkt_meta' */ - find_good_pkt_pointers(other_branch, src_reg, - src_reg->type, false); - mark_pkt_end(this_branch, insn->src_reg, true); - } else { - return false; - } + /* pkt_data >/>= pkt_end, pkt_meta >/>= pkt_data */ + find_good_pkt_pointers(this_branch, dst_reg, dst_reg->type, opcode == BPF_JGE); + mark_pkt_end(other_branch, dst_regno, opcode == BPF_JGT); break; + case BPF_JLT: case BPF_JLE: - if ((dst_reg->type == PTR_TO_PACKET && - src_reg->type == PTR_TO_PACKET_END) || - (dst_reg->type == PTR_TO_PACKET_META && - reg_is_init_pkt_pointer(src_reg, PTR_TO_PACKET))) { - /* pkt_data' <= pkt_end, pkt_meta' <= pkt_data */ - find_good_pkt_pointers(other_branch, dst_reg, - dst_reg->type, false); - mark_pkt_end(this_branch, insn->dst_reg, true); - } else if ((dst_reg->type == PTR_TO_PACKET_END && - src_reg->type == PTR_TO_PACKET) || - (reg_is_init_pkt_pointer(dst_reg, PTR_TO_PACKET) && - src_reg->type == PTR_TO_PACKET_META)) { - /* pkt_end <= pkt_data', pkt_data <= pkt_meta' */ - find_good_pkt_pointers(this_branch, src_reg, - src_reg->type, true); - mark_pkt_end(other_branch, insn->src_reg, false); - } else { - return false; - } + /* pkt_data </<= pkt_end, pkt_meta </<= pkt_data */ + find_good_pkt_pointers(other_branch, dst_reg, dst_reg->type, opcode == BPF_JLT); + mark_pkt_end(this_branch, dst_regno, opcode == BPF_JLE); break; default: return false; -- 2.43.0 ^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [PATCH bpf-next 1/3] bpf: simplify try_match_pkt_pointers() 2024-01-08 13:28 ` [PATCH bpf-next 1/3] bpf: simplify try_match_pkt_pointers() Eduard Zingerman @ 2024-01-09 0:40 ` Andrii Nakryiko 2024-01-09 0:43 ` Andrii Nakryiko 2024-01-09 0:52 ` Eduard Zingerman 0 siblings, 2 replies; 16+ messages in thread From: Andrii Nakryiko @ 2024-01-09 0:40 UTC (permalink / raw) To: Eduard Zingerman Cc: bpf, ast, andrii, daniel, martin.lau, kernel-team, yonghong.song, zenczykowski On Mon, Jan 8, 2024 at 5:28 AM Eduard Zingerman <eddyz87@gmail.com> wrote: > > Reduce number of cases handled in try_match_pkt_pointers() > to <pkt_data> <op> <pkt_end> or <pkt_meta> <op> <pkt_data> > by flipping opcode. > > Suggested-by: Andrii Nakryiko <andrii.nakryiko@gmail.com> > Signed-off-by: Eduard Zingerman <eddyz87@gmail.com> > --- > kernel/bpf/verifier.c | 104 ++++++++++-------------------------------- > 1 file changed, 24 insertions(+), 80 deletions(-) > > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > index adbf330d364b..918e6a7912e2 100644 > --- a/kernel/bpf/verifier.c > +++ b/kernel/bpf/verifier.c > @@ -14677,6 +14677,9 @@ static bool try_match_pkt_pointers(const struct bpf_insn *insn, > struct bpf_verifier_state *this_branch, > struct bpf_verifier_state *other_branch) > { > + int opcode = BPF_OP(insn->code); > + int dst_regno = insn->dst_reg; > + > if (BPF_SRC(insn->code) != BPF_X) > return false; > > @@ -14684,90 +14687,31 @@ static bool try_match_pkt_pointers(const struct bpf_insn *insn, > if (BPF_CLASS(insn->code) == BPF_JMP32) > return false; > > - switch (BPF_OP(insn->code)) { > + if (dst_reg->type == PTR_TO_PACKET_END || > + src_reg->type == PTR_TO_PACKET_META) { > + swap(src_reg, dst_reg); > + dst_regno = insn->src_reg; > + opcode = flip_opcode(opcode); > + } > + > + if ((dst_reg->type != PTR_TO_PACKET || > + src_reg->type != PTR_TO_PACKET_END) && > + (dst_reg->type != PTR_TO_PACKET_META || > + !reg_is_init_pkt_pointer(src_reg, PTR_TO_PACKET))) > + return false; this inverted original condition just breaks my brain, I can't wrap my head around it :) I think the original is easier to reason about because it's two clear allowable patterns for which we do something. I understand that this early exit reduces nestedness, but at least for me it would be simpler to have the original non-inverted condition with a nested switch. > + > + switch (opcode) { > case BPF_JGT: > - if ((dst_reg->type == PTR_TO_PACKET && > - src_reg->type == PTR_TO_PACKET_END) || > - (dst_reg->type == PTR_TO_PACKET_META && > - reg_is_init_pkt_pointer(src_reg, PTR_TO_PACKET))) { > - /* pkt_data' > pkt_end, pkt_meta' > pkt_data */ > - find_good_pkt_pointers(this_branch, dst_reg, > - dst_reg->type, false); > - mark_pkt_end(other_branch, insn->dst_reg, true); > - } else if ((dst_reg->type == PTR_TO_PACKET_END && > - src_reg->type == PTR_TO_PACKET) || > - (reg_is_init_pkt_pointer(dst_reg, PTR_TO_PACKET) && > - src_reg->type == PTR_TO_PACKET_META)) { > - /* pkt_end > pkt_data', pkt_data > pkt_meta' */ > - find_good_pkt_pointers(other_branch, src_reg, > - src_reg->type, true); > - mark_pkt_end(this_branch, insn->src_reg, false); > - } else { > - return false; > - } > - break; [...] ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH bpf-next 1/3] bpf: simplify try_match_pkt_pointers() 2024-01-09 0:40 ` Andrii Nakryiko @ 2024-01-09 0:43 ` Andrii Nakryiko 2024-01-09 0:52 ` Eduard Zingerman 1 sibling, 0 replies; 16+ messages in thread From: Andrii Nakryiko @ 2024-01-09 0:43 UTC (permalink / raw) To: Eduard Zingerman Cc: bpf, ast, andrii, daniel, martin.lau, kernel-team, yonghong.song, zenczykowski On Mon, Jan 8, 2024 at 4:40 PM Andrii Nakryiko <andrii.nakryiko@gmail.com> wrote: > > On Mon, Jan 8, 2024 at 5:28 AM Eduard Zingerman <eddyz87@gmail.com> wrote: > > > > Reduce number of cases handled in try_match_pkt_pointers() > > to <pkt_data> <op> <pkt_end> or <pkt_meta> <op> <pkt_data> > > by flipping opcode. > > > > Suggested-by: Andrii Nakryiko <andrii.nakryiko@gmail.com> > > Signed-off-by: Eduard Zingerman <eddyz87@gmail.com> > > --- > > kernel/bpf/verifier.c | 104 ++++++++++-------------------------------- > > 1 file changed, 24 insertions(+), 80 deletions(-) > > > > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > > index adbf330d364b..918e6a7912e2 100644 > > --- a/kernel/bpf/verifier.c > > +++ b/kernel/bpf/verifier.c > > @@ -14677,6 +14677,9 @@ static bool try_match_pkt_pointers(const struct bpf_insn *insn, > > struct bpf_verifier_state *this_branch, > > struct bpf_verifier_state *other_branch) > > { > > + int opcode = BPF_OP(insn->code); > > + int dst_regno = insn->dst_reg; > > + > > if (BPF_SRC(insn->code) != BPF_X) > > return false; > > > > @@ -14684,90 +14687,31 @@ static bool try_match_pkt_pointers(const struct bpf_insn *insn, > > if (BPF_CLASS(insn->code) == BPF_JMP32) > > return false; > > > > - switch (BPF_OP(insn->code)) { > > + if (dst_reg->type == PTR_TO_PACKET_END || > > + src_reg->type == PTR_TO_PACKET_META) { > > + swap(src_reg, dst_reg); > > + dst_regno = insn->src_reg; > > + opcode = flip_opcode(opcode); > > + } > > + > > + if ((dst_reg->type != PTR_TO_PACKET || > > + src_reg->type != PTR_TO_PACKET_END) && > > + (dst_reg->type != PTR_TO_PACKET_META || > > + !reg_is_init_pkt_pointer(src_reg, PTR_TO_PACKET))) > > + return false; > > this inverted original condition just breaks my brain, I can't wrap my > head around it :) I think the original is easier to reason about > because it's two clear allowable patterns for which we do something. I > understand that this early exit reduces nestedness, but at least for > me it would be simpler to have the original non-inverted condition > with a nested switch. > > > > + > > + switch (opcode) { > > case BPF_JGT: > > - if ((dst_reg->type == PTR_TO_PACKET && > > - src_reg->type == PTR_TO_PACKET_END) || > > - (dst_reg->type == PTR_TO_PACKET_META && > > - reg_is_init_pkt_pointer(src_reg, PTR_TO_PACKET))) { > > - /* pkt_data' > pkt_end, pkt_meta' > pkt_data */ > > - find_good_pkt_pointers(this_branch, dst_reg, > > - dst_reg->type, false); > > - mark_pkt_end(other_branch, insn->dst_reg, true); it seems like you can make a bit of simplification if mark_pkt_end would just accept struct bpf_reg_state * instead of int regn (you won't need to keep track of dst_regno at all, right?) > > - } else if ((dst_reg->type == PTR_TO_PACKET_END && > > - src_reg->type == PTR_TO_PACKET) || > > - (reg_is_init_pkt_pointer(dst_reg, PTR_TO_PACKET) && > > - src_reg->type == PTR_TO_PACKET_META)) { > > - /* pkt_end > pkt_data', pkt_data > pkt_meta' */ > > - find_good_pkt_pointers(other_branch, src_reg, > > - src_reg->type, true); > > - mark_pkt_end(this_branch, insn->src_reg, false); > > - } else { > > - return false; > > - } > > - break; > > [...] ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH bpf-next 1/3] bpf: simplify try_match_pkt_pointers() 2024-01-09 0:40 ` Andrii Nakryiko 2024-01-09 0:43 ` Andrii Nakryiko @ 2024-01-09 0:52 ` Eduard Zingerman 2024-01-09 18:22 ` Andrii Nakryiko 1 sibling, 1 reply; 16+ messages in thread From: Eduard Zingerman @ 2024-01-09 0:52 UTC (permalink / raw) To: Andrii Nakryiko Cc: bpf, ast, andrii, daniel, martin.lau, kernel-team, yonghong.song, zenczykowski On Mon, 2024-01-08 at 16:40 -0800, Andrii Nakryiko wrote: [...] > > @@ -14684,90 +14687,31 @@ static bool try_match_pkt_pointers(const struct bpf_insn *insn, > > if (BPF_CLASS(insn->code) == BPF_JMP32) > > return false; > > > > - switch (BPF_OP(insn->code)) { > > + if (dst_reg->type == PTR_TO_PACKET_END || > > + src_reg->type == PTR_TO_PACKET_META) { > > + swap(src_reg, dst_reg); > > + dst_regno = insn->src_reg; > > + opcode = flip_opcode(opcode); > > + } > > + > > + if ((dst_reg->type != PTR_TO_PACKET || > > + src_reg->type != PTR_TO_PACKET_END) && > > + (dst_reg->type != PTR_TO_PACKET_META || > > + !reg_is_init_pkt_pointer(src_reg, PTR_TO_PACKET))) > > + return false; > > this inverted original condition just breaks my brain, I can't wrap my > head around it :) I think the original is easier to reason about > because it's two clear allowable patterns for which we do something. I > understand that this early exit reduces nestedness, but at least for > me it would be simpler to have the original non-inverted condition > with a nested switch. I'm not sure I understand what you mean by nested switch. If I write it down like below, would that be more clear? bool pkt_data_vs_pkt_end; bool pkt_meta_vs_pkt_data; ... pkt_data_vs_pkt_end = dst_reg->type == PTR_TO_PACKET && src_reg->type == PTR_TO_PACKET_END; pkt_meta_vs_pkt_data = dst_reg->type == PTR_TO_PACKET_META && reg_is_init_pkt_pointer(src_reg, PTR_TO_PACKET); if (!pkt_data_vs_pkt_end && !pkt_meta_vs_pkt_data) return false; > > + > > + switch (opcode) { > > case BPF_JGT: > > - if ((dst_reg->type == PTR_TO_PACKET && > > - src_reg->type == PTR_TO_PACKET_END) || > > - (dst_reg->type == PTR_TO_PACKET_META && > > - reg_is_init_pkt_pointer(src_reg, PTR_TO_PACKET))) { > > - /* pkt_data' > pkt_end, pkt_meta' > pkt_data */ > > - find_good_pkt_pointers(this_branch, dst_reg, > > - dst_reg->type, false); > > - mark_pkt_end(other_branch, insn->dst_reg, true); > it seems like you can make a bit of simplification if mark_pkt_end > would just accept struct bpf_reg_state * instead of int regn (you > won't need to keep track of dst_regno at all, right?) mark_pkt_end() changes the register from either this_branch or other_branch. I can introduce local pointers dst_this/dst_other and swap those, but I'm not sure it's worth it. [...] ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH bpf-next 1/3] bpf: simplify try_match_pkt_pointers() 2024-01-09 0:52 ` Eduard Zingerman @ 2024-01-09 18:22 ` Andrii Nakryiko 0 siblings, 0 replies; 16+ messages in thread From: Andrii Nakryiko @ 2024-01-09 18:22 UTC (permalink / raw) To: Eduard Zingerman Cc: bpf, ast, andrii, daniel, martin.lau, kernel-team, yonghong.song, zenczykowski On Mon, Jan 8, 2024 at 4:52 PM Eduard Zingerman <eddyz87@gmail.com> wrote: > > On Mon, 2024-01-08 at 16:40 -0800, Andrii Nakryiko wrote: > [...] > > > @@ -14684,90 +14687,31 @@ static bool try_match_pkt_pointers(const struct bpf_insn *insn, > > > if (BPF_CLASS(insn->code) == BPF_JMP32) > > > return false; > > > > > > - switch (BPF_OP(insn->code)) { > > > + if (dst_reg->type == PTR_TO_PACKET_END || > > > + src_reg->type == PTR_TO_PACKET_META) { > > > + swap(src_reg, dst_reg); > > > + dst_regno = insn->src_reg; > > > + opcode = flip_opcode(opcode); > > > + } > > > + > > > + if ((dst_reg->type != PTR_TO_PACKET || > > > + src_reg->type != PTR_TO_PACKET_END) && > > > + (dst_reg->type != PTR_TO_PACKET_META || > > > + !reg_is_init_pkt_pointer(src_reg, PTR_TO_PACKET))) > > > + return false; > > > > this inverted original condition just breaks my brain, I can't wrap my > > head around it :) I think the original is easier to reason about > > because it's two clear allowable patterns for which we do something. I > > understand that this early exit reduces nestedness, but at least for > > me it would be simpler to have the original non-inverted condition > > with a nested switch. > > I'm not sure I understand what you mean by nested switch. > If I write it down like below, would that be more clear? > Yes, much more clear. What I had in mind was something like: if (pkt_data_vs_pkt_end || pkt_meta_vs_pkt_data) { switch (opcode) { ... } } But what you have below is easy to follow as well > bool pkt_data_vs_pkt_end; > bool pkt_meta_vs_pkt_data; > ... > pkt_data_vs_pkt_end = > dst_reg->type == PTR_TO_PACKET && src_reg->type == PTR_TO_PACKET_END; > pkt_meta_vs_pkt_data = > dst_reg->type == PTR_TO_PACKET_META && reg_is_init_pkt_pointer(src_reg, PTR_TO_PACKET); > > if (!pkt_data_vs_pkt_end && !pkt_meta_vs_pkt_data) > return false; > > > > + > > > + switch (opcode) { > > > case BPF_JGT: > > > - if ((dst_reg->type == PTR_TO_PACKET && > > > - src_reg->type == PTR_TO_PACKET_END) || > > > - (dst_reg->type == PTR_TO_PACKET_META && > > > - reg_is_init_pkt_pointer(src_reg, PTR_TO_PACKET))) { > > > - /* pkt_data' > pkt_end, pkt_meta' > pkt_data */ > > > - find_good_pkt_pointers(this_branch, dst_reg, > > > - dst_reg->type, false); > > > - mark_pkt_end(other_branch, insn->dst_reg, true); > > > it seems like you can make a bit of simplification if mark_pkt_end > > would just accept struct bpf_reg_state * instead of int regn (you > > won't need to keep track of dst_regno at all, right?) > > mark_pkt_end() changes the register from either this_branch or other_branch. > I can introduce local pointers dst_this/dst_other and swap those, > but I'm not sure it's worth it. Ah, I missed that it's other_branch register. Never mind then, it's fine and it was minor anyways. > > [...] ^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH bpf-next 2/3] bpf: infer packet range for 'if pkt ==/!= pkt_end' comparisons 2024-01-08 13:27 [PATCH bpf-next 0/3] infer packet range for 'if pkt ==/!= pkt_end' instructions Eduard Zingerman 2024-01-08 13:28 ` [PATCH bpf-next 1/3] bpf: simplify try_match_pkt_pointers() Eduard Zingerman @ 2024-01-08 13:28 ` Eduard Zingerman 2024-01-08 13:49 ` Maciej Żenczykowski ` (2 more replies) 2024-01-08 13:28 ` [PATCH bpf-next 3/3] selftests/bpf: test packet range inference for 'if pkt ==/!= pkt_end' Eduard Zingerman 2 siblings, 3 replies; 16+ messages in thread From: Eduard Zingerman @ 2024-01-08 13:28 UTC (permalink / raw) To: bpf, ast Cc: andrii, daniel, martin.lau, kernel-team, yonghong.song, zenczykowski, Eduard Zingerman Extend try_match_pkt_pointers() to handle == and != operations. For instruction: .--------------- pointer to packet with some range R | .--------- pointer to packet end v v if rA == rB goto ... It is valid to infer that R bytes are available in packet. This change should allow verification of BPF generated for C code like below: if (data + 42 != data_end) { ... } Suggested-by: Maciej Żenczykowski <zenczykowski@gmail.com> Link: https://lore.kernel.org/bpf/CAHo-Oow5V2u4ZYvzuR8NmJmFDPNYp0pQDJX66rZqUjFHvhx82A@mail.gmail.com/ Signed-off-by: Eduard Zingerman <eddyz87@gmail.com> --- kernel/bpf/verifier.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 918e6a7912e2..b229ba0ad114 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -14677,6 +14677,7 @@ static bool try_match_pkt_pointers(const struct bpf_insn *insn, struct bpf_verifier_state *this_branch, struct bpf_verifier_state *other_branch) { + struct bpf_verifier_state *eq_branch; int opcode = BPF_OP(insn->code); int dst_regno = insn->dst_reg; @@ -14713,6 +14714,13 @@ static bool try_match_pkt_pointers(const struct bpf_insn *insn, find_good_pkt_pointers(other_branch, dst_reg, dst_reg->type, opcode == BPF_JLT); mark_pkt_end(this_branch, dst_regno, opcode == BPF_JLE); break; + case BPF_JEQ: + case BPF_JNE: + /* pkt_data ==/!= pkt_end, pkt_meta ==/!= pkt_data */ + eq_branch = opcode == BPF_JEQ ? other_branch : this_branch; + find_good_pkt_pointers(eq_branch, dst_reg, dst_reg->type, true); + mark_pkt_end(eq_branch, dst_regno, false); + break; default: return false; } -- 2.43.0 ^ permalink raw reply related [flat|nested] 16+ messages in thread
* Re: [PATCH bpf-next 2/3] bpf: infer packet range for 'if pkt ==/!= pkt_end' comparisons 2024-01-08 13:28 ` [PATCH bpf-next 2/3] bpf: infer packet range for 'if pkt ==/!= pkt_end' comparisons Eduard Zingerman @ 2024-01-08 13:49 ` Maciej Żenczykowski 2024-01-08 13:57 ` Eduard Zingerman 2024-01-09 0:45 ` Andrii Nakryiko 2024-01-09 17:26 ` Yonghong Song 2 siblings, 1 reply; 16+ messages in thread From: Maciej Żenczykowski @ 2024-01-08 13:49 UTC (permalink / raw) To: Eduard Zingerman Cc: bpf, ast, andrii, daniel, martin.lau, kernel-team, yonghong.song (I've looked at all 3 patches, and they seem fine... but I don't claim to understand the intricacies of the verifier/registers/etc well enough to be certain) On Mon, Jan 8, 2024 at 5:28 AM Eduard Zingerman <eddyz87@gmail.com> wrote: > > Extend try_match_pkt_pointers() to handle == and != operations. > For instruction: > > .--------------- pointer to packet with some range R > | .--------- pointer to packet end > v v > if rA == rB goto ... it's possible this would be better without the 'goto' as just 'if (rA == rB) ...' > > It is valid to infer that R bytes are available in packet. > This change should allow verification of BPF generated for > C code like below: > > if (data + 42 != data_end) { ... } this should probably be: if (data + 42 != data_end) return; ... > > Suggested-by: Maciej Żenczykowski <zenczykowski@gmail.com> > Link: https://lore.kernel.org/bpf/CAHo-Oow5V2u4ZYvzuR8NmJmFDPNYp0pQDJX66rZqUjFHvhx82A@mail.gmail.com/ > Signed-off-by: Eduard Zingerman <eddyz87@gmail.com> > --- > kernel/bpf/verifier.c | 8 ++++++++ > 1 file changed, 8 insertions(+) > > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > index 918e6a7912e2..b229ba0ad114 100644 > --- a/kernel/bpf/verifier.c > +++ b/kernel/bpf/verifier.c > @@ -14677,6 +14677,7 @@ static bool try_match_pkt_pointers(const struct bpf_insn *insn, > struct bpf_verifier_state *this_branch, > struct bpf_verifier_state *other_branch) > { > + struct bpf_verifier_state *eq_branch; > int opcode = BPF_OP(insn->code); > int dst_regno = insn->dst_reg; > > @@ -14713,6 +14714,13 @@ static bool try_match_pkt_pointers(const struct bpf_insn *insn, > find_good_pkt_pointers(other_branch, dst_reg, dst_reg->type, opcode == BPF_JLT); > mark_pkt_end(this_branch, dst_regno, opcode == BPF_JLE); > break; > + case BPF_JEQ: > + case BPF_JNE: > + /* pkt_data ==/!= pkt_end, pkt_meta ==/!= pkt_data */ > + eq_branch = opcode == BPF_JEQ ? other_branch : this_branch; > + find_good_pkt_pointers(eq_branch, dst_reg, dst_reg->type, true); > + mark_pkt_end(eq_branch, dst_regno, false); > + break; > default: > return false; > } > -- > 2.43.0 > ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH bpf-next 2/3] bpf: infer packet range for 'if pkt ==/!= pkt_end' comparisons 2024-01-08 13:49 ` Maciej Żenczykowski @ 2024-01-08 13:57 ` Eduard Zingerman 0 siblings, 0 replies; 16+ messages in thread From: Eduard Zingerman @ 2024-01-08 13:57 UTC (permalink / raw) To: Maciej Żenczykowski Cc: bpf, ast, andrii, daniel, martin.lau, kernel-team, yonghong.song On Mon, 2024-01-08 at 05:49 -0800, Maciej Żenczykowski wrote: > (I've looked at all 3 patches, and they seem fine... but I don't claim > to understand the intricacies of the verifier/registers/etc well > enough to be certain) Thank you. > On Mon, Jan 8, 2024 at 5:28 AM Eduard Zingerman <eddyz87@gmail.com> wrote: > > > > Extend try_match_pkt_pointers() to handle == and != operations. > > For instruction: > > > > .--------------- pointer to packet with some range R > > | .--------- pointer to packet end > > v v > > if rA == rB goto ... > > it's possible this would be better without the 'goto' as just 'if (rA > == rB) ...' The idea was to show this as BPF asm instruction, which has syntax 'if rA == rB goto C'. I'll wait for more feedback and submit v2 with updated commit message to make it more clear. > > It is valid to infer that R bytes are available in packet. > > This change should allow verification of BPF generated for > > C code like below: > > > > if (data + 42 != data_end) { ... } > > this should probably be: > if (data + 42 != data_end) return; > ... Makes sense, will update commit message. ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH bpf-next 2/3] bpf: infer packet range for 'if pkt ==/!= pkt_end' comparisons 2024-01-08 13:28 ` [PATCH bpf-next 2/3] bpf: infer packet range for 'if pkt ==/!= pkt_end' comparisons Eduard Zingerman 2024-01-08 13:49 ` Maciej Żenczykowski @ 2024-01-09 0:45 ` Andrii Nakryiko 2024-01-09 0:57 ` Eduard Zingerman 2024-01-09 17:26 ` Yonghong Song 2 siblings, 1 reply; 16+ messages in thread From: Andrii Nakryiko @ 2024-01-09 0:45 UTC (permalink / raw) To: Eduard Zingerman Cc: bpf, ast, andrii, daniel, martin.lau, kernel-team, yonghong.song, zenczykowski On Mon, Jan 8, 2024 at 5:28 AM Eduard Zingerman <eddyz87@gmail.com> wrote: > > Extend try_match_pkt_pointers() to handle == and != operations. > For instruction: > > .--------------- pointer to packet with some range R > | .--------- pointer to packet end > v v > if rA == rB goto ... > > It is valid to infer that R bytes are available in packet. > This change should allow verification of BPF generated for > C code like below: > > if (data + 42 != data_end) { ... } > > Suggested-by: Maciej Żenczykowski <zenczykowski@gmail.com> > Link: https://lore.kernel.org/bpf/CAHo-Oow5V2u4ZYvzuR8NmJmFDPNYp0pQDJX66rZqUjFHvhx82A@mail.gmail.com/ > Signed-off-by: Eduard Zingerman <eddyz87@gmail.com> > --- > kernel/bpf/verifier.c | 8 ++++++++ > 1 file changed, 8 insertions(+) > > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > index 918e6a7912e2..b229ba0ad114 100644 > --- a/kernel/bpf/verifier.c > +++ b/kernel/bpf/verifier.c > @@ -14677,6 +14677,7 @@ static bool try_match_pkt_pointers(const struct bpf_insn *insn, > struct bpf_verifier_state *this_branch, > struct bpf_verifier_state *other_branch) > { > + struct bpf_verifier_state *eq_branch; > int opcode = BPF_OP(insn->code); > int dst_regno = insn->dst_reg; > > @@ -14713,6 +14714,13 @@ static bool try_match_pkt_pointers(const struct bpf_insn *insn, > find_good_pkt_pointers(other_branch, dst_reg, dst_reg->type, opcode == BPF_JLT); > mark_pkt_end(this_branch, dst_regno, opcode == BPF_JLE); > break; > + case BPF_JEQ: > + case BPF_JNE: > + /* pkt_data ==/!= pkt_end, pkt_meta ==/!= pkt_data */ > + eq_branch = opcode == BPF_JEQ ? other_branch : this_branch; > + find_good_pkt_pointers(eq_branch, dst_reg, dst_reg->type, true); > + mark_pkt_end(eq_branch, dst_regno, false); hm... if pkt_data != pkt_end in this_branch, can we really infer whether reg->range is BEYOND_PKT_END or AT_PKT_END? What if it's IN_FRONT_OF_PKT_END? > + break; > default: > return false; > } > -- > 2.43.0 > ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH bpf-next 2/3] bpf: infer packet range for 'if pkt ==/!= pkt_end' comparisons 2024-01-09 0:45 ` Andrii Nakryiko @ 2024-01-09 0:57 ` Eduard Zingerman 2024-01-09 18:32 ` Andrii Nakryiko 0 siblings, 1 reply; 16+ messages in thread From: Eduard Zingerman @ 2024-01-09 0:57 UTC (permalink / raw) To: Andrii Nakryiko Cc: bpf, ast, andrii, daniel, martin.lau, kernel-team, yonghong.song, zenczykowski On Mon, 2024-01-08 at 16:45 -0800, Andrii Nakryiko wrote: [...] > > @@ -14713,6 +14714,13 @@ static bool try_match_pkt_pointers(const struct bpf_insn *insn, > > find_good_pkt_pointers(other_branch, dst_reg, dst_reg->type, opcode == BPF_JLT); > > mark_pkt_end(this_branch, dst_regno, opcode == BPF_JLE); > > break; > > + case BPF_JEQ: > > + case BPF_JNE: > > + /* pkt_data ==/!= pkt_end, pkt_meta ==/!= pkt_data */ > > + eq_branch = opcode == BPF_JEQ ? other_branch : this_branch; > > + find_good_pkt_pointers(eq_branch, dst_reg, dst_reg->type, true); > > + mark_pkt_end(eq_branch, dst_regno, false); > > hm... if pkt_data != pkt_end in this_branch, can we really infer > whether reg->range is BEYOND_PKT_END or AT_PKT_END? What if it's > IN_FRONT_OF_PKT_END? pkt_data != pkt_end in this_branch means that there is an instruction: ... if pkt_data == pkt_end goto <other_branch> ... <this_branch> ... the 'eq_branch' would be set to 'other_branch' and AT_PKT_END would be set for dst register in 'other_branch'. What's wrong with this? Or did you mean something else? ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH bpf-next 2/3] bpf: infer packet range for 'if pkt ==/!= pkt_end' comparisons 2024-01-09 0:57 ` Eduard Zingerman @ 2024-01-09 18:32 ` Andrii Nakryiko 0 siblings, 0 replies; 16+ messages in thread From: Andrii Nakryiko @ 2024-01-09 18:32 UTC (permalink / raw) To: Eduard Zingerman Cc: bpf, ast, andrii, daniel, martin.lau, kernel-team, yonghong.song, zenczykowski On Mon, Jan 8, 2024 at 4:57 PM Eduard Zingerman <eddyz87@gmail.com> wrote: > > On Mon, 2024-01-08 at 16:45 -0800, Andrii Nakryiko wrote: > [...] > > > @@ -14713,6 +14714,13 @@ static bool try_match_pkt_pointers(const struct bpf_insn *insn, > > > find_good_pkt_pointers(other_branch, dst_reg, dst_reg->type, opcode == BPF_JLT); > > > mark_pkt_end(this_branch, dst_regno, opcode == BPF_JLE); > > > break; > > > + case BPF_JEQ: > > > + case BPF_JNE: > > > + /* pkt_data ==/!= pkt_end, pkt_meta ==/!= pkt_data */ > > > + eq_branch = opcode == BPF_JEQ ? other_branch : this_branch; > > > + find_good_pkt_pointers(eq_branch, dst_reg, dst_reg->type, true); > > > + mark_pkt_end(eq_branch, dst_regno, false); > > > > hm... if pkt_data != pkt_end in this_branch, can we really infer > > whether reg->range is BEYOND_PKT_END or AT_PKT_END? What if it's > > IN_FRONT_OF_PKT_END? > > pkt_data != pkt_end in this_branch means that there is an instruction: > > ... > if pkt_data == pkt_end goto <other_branch> > ... <this_branch> ... > > the 'eq_branch' would be set to 'other_branch' and AT_PKT_END would be set > for dst register in 'other_branch'. What's wrong with this? > Or did you mean something else? Well, first off, I'm very unfamiliar with all these pkt_xxx registers, so excuse me for stupid questions. I guess what got me confused was that find_good_pkt_pointer() and mark_pkt_end() previously (for GT/GE/LT/LE) were working on opposing branches. But here I see they work on the same "equal" branch. But now I'm wondering what's the point of even calling find_good_pkt_pointer()? Is there a scenario where it can derive new information from JEQ/JNE? ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH bpf-next 2/3] bpf: infer packet range for 'if pkt ==/!= pkt_end' comparisons 2024-01-08 13:28 ` [PATCH bpf-next 2/3] bpf: infer packet range for 'if pkt ==/!= pkt_end' comparisons Eduard Zingerman 2024-01-08 13:49 ` Maciej Żenczykowski 2024-01-09 0:45 ` Andrii Nakryiko @ 2024-01-09 17:26 ` Yonghong Song 2024-01-10 1:07 ` Eduard Zingerman 2 siblings, 1 reply; 16+ messages in thread From: Yonghong Song @ 2024-01-09 17:26 UTC (permalink / raw) To: Eduard Zingerman, bpf, ast Cc: andrii, daniel, martin.lau, kernel-team, zenczykowski On 1/8/24 5:28 AM, Eduard Zingerman wrote: > Extend try_match_pkt_pointers() to handle == and != operations. > For instruction: > > .--------------- pointer to packet with some range R > | .--------- pointer to packet end > v v > if rA == rB goto ... > > It is valid to infer that R bytes are available in packet. > This change should allow verification of BPF generated for > C code like below: > > if (data + 42 != data_end) { ... } > > Suggested-by: Maciej Żenczykowski <zenczykowski@gmail.com> > Link: https://lore.kernel.org/bpf/CAHo-Oow5V2u4ZYvzuR8NmJmFDPNYp0pQDJX66rZqUjFHvhx82A@mail.gmail.com/ > Signed-off-by: Eduard Zingerman <eddyz87@gmail.com> > --- > kernel/bpf/verifier.c | 8 ++++++++ > 1 file changed, 8 insertions(+) > > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c > index 918e6a7912e2..b229ba0ad114 100644 > --- a/kernel/bpf/verifier.c > +++ b/kernel/bpf/verifier.c > @@ -14677,6 +14677,7 @@ static bool try_match_pkt_pointers(const struct bpf_insn *insn, > struct bpf_verifier_state *this_branch, > struct bpf_verifier_state *other_branch) > { > + struct bpf_verifier_state *eq_branch; > int opcode = BPF_OP(insn->code); > int dst_regno = insn->dst_reg; > > @@ -14713,6 +14714,13 @@ static bool try_match_pkt_pointers(const struct bpf_insn *insn, > find_good_pkt_pointers(other_branch, dst_reg, dst_reg->type, opcode == BPF_JLT); > mark_pkt_end(this_branch, dst_regno, opcode == BPF_JLE); > break; > + case BPF_JEQ: > + case BPF_JNE: > + /* pkt_data ==/!= pkt_end, pkt_meta ==/!= pkt_data */ > + eq_branch = opcode == BPF_JEQ ? other_branch : this_branch; > + find_good_pkt_pointers(eq_branch, dst_reg, dst_reg->type, true); > + mark_pkt_end(eq_branch, dst_regno, false); > + break; What will happen if there are multiple BPF_JEQ/BPF_JNE? I made a change to one of tests in patch 3: +SEC("tc") +__success __log_level(2) +__msg("if r3 != r2 goto pc+3 ; R2_w=pkt_end() R3_w=pkt(off=8,r=0xffffffffffffffff)") +__naked void data_plus_const_neq_pkt_end(void) +{ + asm volatile (" \ + r9 = r1; \ + r1 = *(u32*)(r9 + %[__sk_buff_data]); \ + r2 = *(u32*)(r9 + %[__sk_buff_data_end]); \ + r3 = r1; \ + r3 += 8; \ + if r3 != r2 goto 1f; \ + r3 += 8; \ + if r3 != r2 goto 1f; \ + r1 = *(u64 *)(r1 + 0); \ +1: \ + r0 = 0; \ + exit; \ +" : + : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)), + __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end)) + : __clobber_all); +} The verifier output: func#0 @0 Global function data_plus_const_neq_pkt_end() doesn't return scalar. Only those are supported. 0: R1=ctx() R10=fp0 ; asm volatile (" \ 0: (bf) r9 = r1 ; R1=ctx() R9_w=ctx() 1: (61) r1 = *(u32 *)(r9 +76) ; R1_w=pkt(r=0) R9_w=ctx() 2: (61) r2 = *(u32 *)(r9 +80) ; R2_w=pkt_end() R9_w=ctx() 3: (bf) r3 = r1 ; R1_w=pkt(r=0) R3_w=pkt(r=0) 4: (07) r3 += 8 ; R3_w=pkt(off=8,r=0) 5: (5d) if r3 != r2 goto pc+3 ; R2_w=pkt_end() R3_w=pkt(off=8,r=0xffffffffffffffff) 6: (07) r3 += 8 ; R3_w=pkt(off=16,r=0xffffffffffffffff) 7: (5d) if r3 != r2 goto pc+1 ; R2_w=pkt_end() R3_w=pkt(off=16,r=0xffffffffffffffff) 8: (79) r1 = *(u64 *)(r1 +0) ; R1=scalar() 9: (b7) r0 = 0 ; R0_w=0 10: (95) exit from 7 to 9: safe from 5 to 9: safe processed 13 insns (limit 1000000) max_states_per_insn 0 total_states 1 peak_states 1 mark_read 0 insn 5, for this_branch (straight one), r3 range will be 8 and assuming pkt_end is 8. insn 7, r3 range becomes 18 and then we assume pkt_end is 16. I guess we should handle this case. For branch 5 and 7, it cannot be that both will be true. > default: > return false; > } ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH bpf-next 2/3] bpf: infer packet range for 'if pkt ==/!= pkt_end' comparisons 2024-01-09 17:26 ` Yonghong Song @ 2024-01-10 1:07 ` Eduard Zingerman 2024-01-10 18:23 ` Eduard Zingerman 0 siblings, 1 reply; 16+ messages in thread From: Eduard Zingerman @ 2024-01-10 1:07 UTC (permalink / raw) To: Yonghong Song, bpf, ast Cc: andrii, daniel, martin.lau, kernel-team, zenczykowski On Tue, 2024-01-09 at 09:26 -0800, Yonghong Song wrote: [...] > > What will happen if there are multiple BPF_JEQ/BPF_JNE? I made a change to one of tests > in patch 3: > > +SEC("tc") > +__success __log_level(2) > +__msg("if r3 != r2 goto pc+3 ; R2_w=pkt_end() R3_w=pkt(off=8,r=0xffffffffffffffff)") > +__naked void data_plus_const_neq_pkt_end(void) > +{ > + asm volatile (" \ > + r9 = r1; \ > + r1 = *(u32*)(r9 + %[__sk_buff_data]); \ > + r2 = *(u32*)(r9 + %[__sk_buff_data_end]); \ > + r3 = r1; \ > + r3 += 8; \ > + if r3 != r2 goto 1f; \ > + r3 += 8; \ > + if r3 != r2 goto 1f; \ > + r1 = *(u64 *)(r1 + 0); \ > +1: \ > + r0 = 0; \ > + exit; \ > +" : > + : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)), > + __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end)) > + : __clobber_all); > +} > > > The verifier output: > func#0 @0 > Global function data_plus_const_neq_pkt_end() doesn't return scalar. Only those are supported. > 0: R1=ctx() R10=fp0 > ; asm volatile (" \ > 0: (bf) r9 = r1 ; R1=ctx() R9_w=ctx() > 1: (61) r1 = *(u32 *)(r9 +76) ; R1_w=pkt(r=0) R9_w=ctx() > 2: (61) r2 = *(u32 *)(r9 +80) ; R2_w=pkt_end() R9_w=ctx() > 3: (bf) r3 = r1 ; R1_w=pkt(r=0) R3_w=pkt(r=0) > 4: (07) r3 += 8 ; R3_w=pkt(off=8,r=0) > 5: (5d) if r3 != r2 goto pc+3 ; R2_w=pkt_end() R3_w=pkt(off=8,r=0xffffffffffffffff) > 6: (07) r3 += 8 ; R3_w=pkt(off=16,r=0xffffffffffffffff) > 7: (5d) if r3 != r2 goto pc+1 ; R2_w=pkt_end() R3_w=pkt(off=16,r=0xffffffffffffffff) > 8: (79) r1 = *(u64 *)(r1 +0) ; R1=scalar() > 9: (b7) r0 = 0 ; R0_w=0 > 10: (95) exit > > from 7 to 9: safe > > from 5 to 9: safe > processed 13 insns (limit 1000000) max_states_per_insn 0 total_states 1 peak_states 1 mark_read 0 > > insn 5, for this_branch (straight one), r3 range will be 8 and assuming pkt_end is 8. > insn 7, r3 range becomes 18 and then we assume pkt_end is 16. > > I guess we should handle this case. For branch 5 and 7, it cannot be that both will be true. This is an interesting case. reg->range is set to AT_PKT_END or BEYOND_PKT_END only in try_match_pkt_pointers() (in mark_pkt_end() call). And this range mark appears not to be reset by += operation (which might add a negative number as well). So, once r3 is marked AT_PKT_END it would remain so even after r3 += 8, which is obviously not true. Not sure what to do yet. ^ permalink raw reply [flat|nested] 16+ messages in thread
* Re: [PATCH bpf-next 2/3] bpf: infer packet range for 'if pkt ==/!= pkt_end' comparisons 2024-01-10 1:07 ` Eduard Zingerman @ 2024-01-10 18:23 ` Eduard Zingerman 0 siblings, 0 replies; 16+ messages in thread From: Eduard Zingerman @ 2024-01-10 18:23 UTC (permalink / raw) To: Yonghong Song, bpf, ast Cc: andrii, daniel, martin.lau, kernel-team, zenczykowski On Wed, 2024-01-10 at 03:07 +0200, Eduard Zingerman wrote: > On Tue, 2024-01-09 at 09:26 -0800, Yonghong Song wrote: > [...] > > > > What will happen if there are multiple BPF_JEQ/BPF_JNE? I made a change to one of tests > > in patch 3: > > > > +SEC("tc") > > +__success __log_level(2) > > +__msg("if r3 != r2 goto pc+3 ; R2_w=pkt_end() R3_w=pkt(off=8,r=0xffffffffffffffff)") > > +__naked void data_plus_const_neq_pkt_end(void) > > +{ > > + asm volatile (" \ > > + r9 = r1; \ > > + r1 = *(u32*)(r9 + %[__sk_buff_data]); \ > > + r2 = *(u32*)(r9 + %[__sk_buff_data_end]); \ > > + r3 = r1; \ > > + r3 += 8; \ > > + if r3 != r2 goto 1f; \ > > + r3 += 8; \ > > + if r3 != r2 goto 1f; \ > > + r1 = *(u64 *)(r1 + 0); \ > > +1: \ > > + r0 = 0; \ > > + exit; \ > > +" : > > + : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)), > > + __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end)) > > + : __clobber_all); > > +} > > > > > > The verifier output: > > func#0 @0 > > Global function data_plus_const_neq_pkt_end() doesn't return scalar. Only those are supported. > > 0: R1=ctx() R10=fp0 > > ; asm volatile (" \ > > 0: (bf) r9 = r1 ; R1=ctx() R9_w=ctx() > > 1: (61) r1 = *(u32 *)(r9 +76) ; R1_w=pkt(r=0) R9_w=ctx() > > 2: (61) r2 = *(u32 *)(r9 +80) ; R2_w=pkt_end() R9_w=ctx() > > 3: (bf) r3 = r1 ; R1_w=pkt(r=0) R3_w=pkt(r=0) > > 4: (07) r3 += 8 ; R3_w=pkt(off=8,r=0) > > 5: (5d) if r3 != r2 goto pc+3 ; R2_w=pkt_end() R3_w=pkt(off=8,r=0xffffffffffffffff) > > 6: (07) r3 += 8 ; R3_w=pkt(off=16,r=0xffffffffffffffff) > > 7: (5d) if r3 != r2 goto pc+1 ; R2_w=pkt_end() R3_w=pkt(off=16,r=0xffffffffffffffff) > > 8: (79) r1 = *(u64 *)(r1 +0) ; R1=scalar() > > 9: (b7) r0 = 0 ; R0_w=0 > > 10: (95) exit > > > > from 7 to 9: safe > > > > from 5 to 9: safe > > processed 13 insns (limit 1000000) max_states_per_insn 0 total_states 1 peak_states 1 mark_read 0 > > > > insn 5, for this_branch (straight one), r3 range will be 8 and assuming pkt_end is 8. > > insn 7, r3 range becomes 18 and then we assume pkt_end is 16. > > > > I guess we should handle this case. For branch 5 and 7, it cannot be that both will be true. > > This is an interesting case. > reg->range is set to AT_PKT_END or BEYOND_PKT_END only in > try_match_pkt_pointers() (in mark_pkt_end() call). > And this range mark appears not to be reset by += operation > (which might add a negative number as well). > So, once r3 is marked AT_PKT_END it would remain so > even after r3 += 8, which is obviously not true. > Not sure what to do yet. Here is another example which is currently not handled correctly, even w/o my patch: SEC("tc") __success __naked void pkt_vs_pkt_end_with_bound_change(void) { asm volatile (" \ r9 = r1; \ r0 = 0; \ r1 = *(u32*)(r9 + %[__sk_buff_data]); \ r2 = *(u32*)(r9 + %[__sk_buff_data_end]); \ r3 = r1; \ r3 += 8; \ if r3 <= r2 goto 1f; \ r3 -= 8; \ if r3 >= r2 goto 1f; \ r4 = *(u64 *)(r1 + 0); \ 1: exit; \ " : : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)), __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end)) : __clobber_all); } Verifier log: 0: R1=ctx() R10=fp0 ; asm volatile (" \ 0: (bf) r9 = r1 ; R1=ctx() R9_w=ctx() 1: (b7) r0 = 0 ; R0_w=0 2: (61) r1 = *(u32 *)(r9 +76) ; R1_w=pkt(r=0) R9_w=ctx() 3: (61) r2 = *(u32 *)(r9 +80) ; R2_w=pkt_end() R9_w=ctx() 4: (bf) r3 = r1 ; R1_w=pkt(r=0) R3_w=pkt(r=0) 5: (07) r3 += 8 ; R3_w=pkt(off=8,r=0) 6: (bd) if r3 <= r2 goto pc+3 ; R2_w=pkt_end() R3_w=pkt(off=8,r=0xfffffffffffffffe) 7: (17) r3 -= 8 ; R3_w=pkt(r=0xfffffffffffffffe) 8: (3d) if r3 >= r2 goto pc+1 ; R2_w=pkt_end() R3_w=pkt(r=0xfffffffffffffffe) 10: (95) exit from 6 to 10: safe processed 11 insns (limit 1000000) max_states_per_insn 0 total_states 1 peak_states 1 mark_read 1 At (6) for this_branch r3 is marked BEYOND_PKT_END, packet range is known to be 8; at (7) it is changed to point back to start of the packet; at (8) is_pkt_ptr_branch_taken() incorrectly predicts that r3 >= r2 (r3 - packet start, r2 - packet end). ^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH bpf-next 3/3] selftests/bpf: test packet range inference for 'if pkt ==/!= pkt_end' 2024-01-08 13:27 [PATCH bpf-next 0/3] infer packet range for 'if pkt ==/!= pkt_end' instructions Eduard Zingerman 2024-01-08 13:28 ` [PATCH bpf-next 1/3] bpf: simplify try_match_pkt_pointers() Eduard Zingerman 2024-01-08 13:28 ` [PATCH bpf-next 2/3] bpf: infer packet range for 'if pkt ==/!= pkt_end' comparisons Eduard Zingerman @ 2024-01-08 13:28 ` Eduard Zingerman 2 siblings, 0 replies; 16+ messages in thread From: Eduard Zingerman @ 2024-01-08 13:28 UTC (permalink / raw) To: bpf, ast Cc: andrii, daniel, martin.lau, kernel-team, yonghong.song, zenczykowski, Eduard Zingerman Check that the following cases are handled by verifier: - packet access after 'if pkt_data + const != pkt_end' (positive and negative cases); - packet access after 'if pkt_data + const == pkt_end' (positive and negative cases); - packet metadata access after 'if pkt_meta + const != pkt_data'; - packet metadata access after 'if pkt_data != pkt_meta + const'; Signed-off-by: Eduard Zingerman <eddyz87@gmail.com> --- .../bpf/progs/verifier_direct_packet_access.c | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) diff --git a/tools/testing/selftests/bpf/progs/verifier_direct_packet_access.c b/tools/testing/selftests/bpf/progs/verifier_direct_packet_access.c index be95570ab382..0ee99d7bc846 100644 --- a/tools/testing/selftests/bpf/progs/verifier_direct_packet_access.c +++ b/tools/testing/selftests/bpf/progs/verifier_direct_packet_access.c @@ -800,4 +800,142 @@ l0_%=: /* exit(0) */ \ : __clobber_all); } +SEC("tc") +__success __log_level(2) +__msg("if r3 != r2 goto pc+1 ; R2_w=pkt_end() R3_w=pkt(off=8,r=0xffffffffffffffff)") +__naked void data_plus_const_neq_pkt_end(void) +{ + asm volatile (" \ + r9 = r1; \ + r1 = *(u32*)(r9 + %[__sk_buff_data]); \ + r2 = *(u32*)(r9 + %[__sk_buff_data_end]); \ + r3 = r1; \ + r3 += 8; \ + if r3 != r2 goto 1f; \ + r1 = *(u64 *)(r1 + 0); \ +1: \ + r0 = 0; \ + exit; \ +" : + : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)), + __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end)) + : __clobber_all); +} + +SEC("tc") +__failure __log_level(2) +__msg("8: R1=pkt(r=0) R2=pkt_end() R3=pkt(off=8,r=0)") +__msg("invalid access to packet, off=0 size=8, R1(id=0,off=0,r=0)") +__naked void data_plus_const_neq_pkt_end_negative(void) +{ + asm volatile (" \ + r9 = r1; \ + r1 = *(u32*)(r9 + %[__sk_buff_data]); \ + r2 = *(u32*)(r9 + %[__sk_buff_data_end]); \ + r3 = r1; \ + r3 += 8; \ + if r3 != r2 goto 1f; \ + r0 = 0; \ + exit; \ +1: \ + r1 = *(u64 *)(r1 + 0); \ + r0 = 0; \ + exit; \ +" : + : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)), + __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end)) + : __clobber_all); +} + +SEC("tc") +__success __log_level(2) +__msg("8: R1=pkt(r=9) R2=pkt_end() R3=pkt(off=8,r=0xffffffffffffffff)") +__naked void data_plus_const_eq_pkt_end(void) +{ + asm volatile (" \ + r9 = r1; \ + r1 = *(u32*)(r9 + %[__sk_buff_data]); \ + r2 = *(u32*)(r9 + %[__sk_buff_data_end]); \ + r3 = r1; \ + r3 += 8; \ + if r3 == r2 goto 1f; \ + r0 = 0; \ + exit; \ +1: \ + r1 = *(u64 *)(r1 + 0); \ + r0 = 0; \ + exit; \ +" : + : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)), + __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end)) + : __clobber_all); +} + +SEC("tc") +__failure __log_level(2) +__msg("if r3 == r2 goto pc+3 ; R2_w=pkt_end() R3_w=pkt(off=8,r=0)") +__msg("invalid access to packet, off=0 size=8, R1(id=0,off=0,r=0)") +__naked void data_plus_const_eq_pkt_end_negative(void) +{ + asm volatile (" \ + r9 = r1; \ + r1 = *(u32*)(r9 + %[__sk_buff_data]); \ + r2 = *(u32*)(r9 + %[__sk_buff_data_end]); \ + r3 = r1; \ + r3 += 8; \ + if r3 == r2 goto 1f; \ + r1 = *(u64 *)(r1 + 0); \ + r0 = 0; \ + exit; \ +1: \ + r0 = 0; \ + exit; \ +" : + : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)), + __imm_const(__sk_buff_data_end, offsetof(struct __sk_buff, data_end)) + : __clobber_all); +} + +SEC("tc") +__success +__naked void pkt_meta_plus_const_neq_pkt_data(void) +{ + asm volatile (" \ + r9 = r1; \ + r1 = *(u32*)(r9 + %[__sk_buff_data_meta]); \ + r2 = *(u32*)(r9 + %[__sk_buff_data]); \ + r3 = r1; \ + r3 += 8; \ + if r3 != r2 goto 1f; \ + r1 = *(u64 *)(r1 + 0); \ +1: \ + r0 = 0; \ + exit; \ +" : + : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)), + __imm_const(__sk_buff_data_meta, offsetof(struct __sk_buff, data_meta)) + : __clobber_all); +} + +SEC("tc") +__success +__naked void pkt_data_neq_pkt_meta_plus_const(void) +{ + asm volatile (" \ + r9 = r1; \ + r1 = *(u32*)(r9 + %[__sk_buff_data_meta]); \ + r2 = *(u32*)(r9 + %[__sk_buff_data]); \ + r3 = r1; \ + r3 += 8; \ + if r2 != r3 goto 1f; \ + r1 = *(u64 *)(r1 + 0); \ +1: \ + r0 = 0; \ + exit; \ +" : + : __imm_const(__sk_buff_data, offsetof(struct __sk_buff, data)), + __imm_const(__sk_buff_data_meta, offsetof(struct __sk_buff, data_meta)) + : __clobber_all); +} + char _license[] SEC("license") = "GPL"; -- 2.43.0 ^ permalink raw reply related [flat|nested] 16+ messages in thread
end of thread, other threads:[~2024-01-10 18:23 UTC | newest] Thread overview: 16+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2024-01-08 13:27 [PATCH bpf-next 0/3] infer packet range for 'if pkt ==/!= pkt_end' instructions Eduard Zingerman 2024-01-08 13:28 ` [PATCH bpf-next 1/3] bpf: simplify try_match_pkt_pointers() Eduard Zingerman 2024-01-09 0:40 ` Andrii Nakryiko 2024-01-09 0:43 ` Andrii Nakryiko 2024-01-09 0:52 ` Eduard Zingerman 2024-01-09 18:22 ` Andrii Nakryiko 2024-01-08 13:28 ` [PATCH bpf-next 2/3] bpf: infer packet range for 'if pkt ==/!= pkt_end' comparisons Eduard Zingerman 2024-01-08 13:49 ` Maciej Żenczykowski 2024-01-08 13:57 ` Eduard Zingerman 2024-01-09 0:45 ` Andrii Nakryiko 2024-01-09 0:57 ` Eduard Zingerman 2024-01-09 18:32 ` Andrii Nakryiko 2024-01-09 17:26 ` Yonghong Song 2024-01-10 1:07 ` Eduard Zingerman 2024-01-10 18:23 ` Eduard Zingerman 2024-01-08 13:28 ` [PATCH bpf-next 3/3] selftests/bpf: test packet range inference for 'if pkt ==/!= pkt_end' Eduard Zingerman
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox