BPF List
 help / color / mirror / Atom feed
* [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

* [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

* [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

* 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 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 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 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 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-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 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

* 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-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

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