[PATCH bpf-next 2/2] selftests/bpf: Add a test with bpf_unreachable() kfunc

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



The test case is from [1] reported by Marc Suñé. When compiled with [2],
the object code looks like:

  0000000000000000 <repro>:
  ; {
       0:       bf 16 00 00 00 00 00 00 r6 = r1
  ;       bpf_printk("Start");
       1:       18 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x0 ll
                0000000000000008:  R_BPF_64_64  .rodata
       3:       b4 02 00 00 06 00 00 00 w2 = 0x6
       4:       85 00 00 00 06 00 00 00 call 0x6
  ; DEFINE_FUNC_CTX_POINTER(data)
       5:       61 61 4c 00 00 00 00 00 w1 = *(u32 *)(r6 + 0x4c)
  ;       bpf_printk("pre ipv6_hdrlen_offset");
       6:       18 01 00 00 06 00 00 00 00 00 00 00 00 00 00 00 r1 = 0x6 ll
                0000000000000030:  R_BPF_64_64  .rodata
       8:       b4 02 00 00 17 00 00 00 w2 = 0x17
       9:       85 00 00 00 06 00 00 00 call 0x6
      10:       85 10 00 00 ff ff ff ff call -0x1
                0000000000000050:  R_BPF_64_32  bpf_unreachable

You can see the last insn is bpf_unreachable() func and bpf verifier
can take advantage of such information and emits the following error
message:
  (85) call bpf_unreachable#74465
  last insn is bpf_unreachable, due to uninitialized var?

  [1] https://github.com/msune/clang_bpf/blob/main/Makefile#L3
  [2] https://github.com/llvm/llvm-project/pull/131731

Signed-off-by: Yonghong Song <yonghong.song@xxxxxxxxx>
---
 .../selftests/bpf/prog_tests/verifier.c       |   2 +
 .../selftests/bpf/progs/verifier_uninit_var.c | 203 ++++++++++++++++++
 2 files changed, 205 insertions(+)
 create mode 100644 tools/testing/selftests/bpf/progs/verifier_uninit_var.c

diff --git a/tools/testing/selftests/bpf/prog_tests/verifier.c b/tools/testing/selftests/bpf/prog_tests/verifier.c
index e66a57970d28..bc2765d130c0 100644
--- a/tools/testing/selftests/bpf/prog_tests/verifier.c
+++ b/tools/testing/selftests/bpf/prog_tests/verifier.c
@@ -87,6 +87,7 @@
 #include "verifier_tailcall_jit.skel.h"
 #include "verifier_typedef.skel.h"
 #include "verifier_uninit.skel.h"
+#include "verifier_uninit_var.skel.h"
 #include "verifier_unpriv.skel.h"
 #include "verifier_unpriv_perf.skel.h"
 #include "verifier_value_adj_spill.skel.h"
@@ -220,6 +221,7 @@ void test_verifier_subreg(void)               { RUN(verifier_subreg); }
 void test_verifier_tailcall_jit(void)         { RUN(verifier_tailcall_jit); }
 void test_verifier_typedef(void)              { RUN(verifier_typedef); }
 void test_verifier_uninit(void)               { RUN(verifier_uninit); }
+void test_verifier_uninit_var(void)           { RUN(verifier_uninit_var); }
 void test_verifier_unpriv(void)               { RUN(verifier_unpriv); }
 void test_verifier_unpriv_perf(void)          { RUN(verifier_unpriv_perf); }
 void test_verifier_value_adj_spill(void)      { RUN(verifier_value_adj_spill); }
diff --git a/tools/testing/selftests/bpf/progs/verifier_uninit_var.c b/tools/testing/selftests/bpf/progs/verifier_uninit_var.c
new file mode 100644
index 000000000000..5ec01b492623
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/verifier_uninit_var.c
@@ -0,0 +1,203 @@
+#include <linux/bpf.h>
+#include <linux/pkt_cls.h>
+#include <linux/if_ether.h>
+#include <linux/in.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/udp.h>
+#include <linux/tcp.h>
+#include <stdbool.h>
+#include <linux/icmpv6.h>
+#include <linux/in.h>
+
+#include <bpf/bpf_endian.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+#include "bpf_misc.h"
+
+union macaddr {
+	struct {
+		__u32 p1;
+		__u16 p2;
+	};
+	__u8 addr[6];
+};
+
+union v6addr {
+	struct {
+		__u32 p1;
+		__u32 p2;
+		__u32 p3;
+		__u32 p4;
+	};
+	struct {
+		__u64 d1;
+		__u64 d2;
+	};
+	__u8 addr[16];
+};
+
+/* Number of extension headers that can be skipped */
+#define IPV6_MAX_HEADERS 4
+
+#define NEXTHDR_HOP             0       /* Hop-by-hop option header. */
+#define NEXTHDR_TCP             6       /* TCP segment. */
+#define NEXTHDR_UDP             17      /* UDP message. */
+#define NEXTHDR_IPV6            41      /* IPv6 in IPv6 */
+#define NEXTHDR_ROUTING         43      /* Routing header. */
+#define NEXTHDR_FRAGMENT        44      /* Fragmentation/reassembly header. */
+#define NEXTHDR_GRE             47      /* GRE header. */
+#define NEXTHDR_ESP             50      /* Encapsulating security payload. */
+#define NEXTHDR_AUTH            51      /* Authentication header. */
+#define NEXTHDR_ICMP            58      /* ICMP for IPv6. */
+#define NEXTHDR_NONE            59      /* No next header */
+#define NEXTHDR_DEST            60      /* Destination options header. */
+#define NEXTHDR_SCTP            132     /* SCTP message. */
+#define NEXTHDR_MOBILITY        135     /* Mobility header. */
+
+#define NEXTHDR_MAX             255
+
+#define IPV6_SADDR_OFF		offsetof(struct ipv6hdr, saddr)
+#define IPV6_DADDR_OFF		offsetof(struct ipv6hdr, daddr)
+
+#define NEXTHDR_ICMP 58
+#define ICMP6_NS_MSG_TYPE		135
+
+#define DROP_INVALID		-134
+#define DROP_INVALID_EXTHDR	-156
+#define DROP_FRAG_NOSUPPORT	-157
+
+#define ctx_load_bytes		skb_load_bytes
+
+#define DEFINE_FUNC_CTX_POINTER(FIELD)						\
+static __always_inline void *							\
+ctx_ ## FIELD(const struct __sk_buff *ctx)					\
+{										\
+	void *ptr;								\
+										\
+	/* LLVM may generate u32 assignments of ctx->{data,data_end,data_meta}.	\
+	 * With this inline asm, LLVM loses track of the fact this field is on	\
+	 * 32 bits.								\
+	 */									\
+	asm volatile("%0 = *(u32 *)(%1 + %2)"					\
+		     : "=r"(ptr)						\
+		     : "r"(ctx), "i"(offsetof(struct __sk_buff, FIELD)));	\
+	return ptr;								\
+}
+/* This defines ctx_data(). */
+DEFINE_FUNC_CTX_POINTER(data)
+#undef DEFINE_FUNC_CTX_POINTER
+
+
+static __always_inline int ipv6_optlen(const struct ipv6_opt_hdr *opthdr)
+{
+	return (opthdr->hdrlen + 1) << 3;
+}
+
+static __always_inline int ipv6_authlen(const struct ipv6_opt_hdr *opthdr)
+{
+	return (opthdr->hdrlen + 2) << 2;
+}
+
+static __always_inline int ipv6_hdrlen_offset(struct __sk_buff *ctx,
+					      __u8 *nexthdr, int l3_off)
+{
+	int i, len = sizeof(struct ipv6hdr);
+	struct ipv6_opt_hdr opthdr __attribute__((aligned(8)));
+	__u8 nh = *nexthdr;
+
+#pragma unroll
+	for (i = 0; i < IPV6_MAX_HEADERS; i++) {
+		switch (nh) {
+		case NEXTHDR_NONE:
+			return DROP_INVALID_EXTHDR;
+
+		case NEXTHDR_FRAGMENT:
+			return DROP_FRAG_NOSUPPORT;
+
+		case NEXTHDR_HOP:
+		case NEXTHDR_ROUTING:
+		case NEXTHDR_AUTH:
+		case NEXTHDR_DEST:
+			if (bpf_skb_load_bytes(ctx, l3_off + len, &opthdr,
+					   sizeof(opthdr)) < 0)
+				return DROP_INVALID;
+
+			if (nh == NEXTHDR_AUTH)
+				len += ipv6_authlen(&opthdr);
+			else
+				len += ipv6_optlen(&opthdr);
+
+			nh = opthdr.nexthdr;
+			break;
+
+		default:
+			bpf_printk("OKOK %d, len: %d", *nexthdr, len);
+			*nexthdr = nh;
+			return len;
+		}
+	}
+
+	bpf_printk("KO INVALID EXTHDR");
+
+	/* Reached limit of supported extension headers */
+	return DROP_INVALID_EXTHDR;
+}
+static __always_inline
+bool icmp6_ndisc_validate(struct __sk_buff *ctx, struct ipv6hdr *ip6,
+			  union macaddr *mac, union macaddr *smac,
+			  union v6addr *sip, union v6addr *tip)
+{
+	__u8 nexthdr;
+	struct icmp6hdr *icmp;
+	int l3_off, l4_off;
+
+	l3_off = (__u8 *)ip6 - (__u8 *)ctx_data(ctx);
+	bpf_printk("pre ipv6_hdrlen_offset");
+	l4_off = ipv6_hdrlen_offset(ctx, &nexthdr, l3_off);
+	bpf_printk("post ipv6_hdrlen_offset");
+
+	if (l4_off < 0 || nexthdr != NEXTHDR_ICMP) {
+		bpf_printk("KO 1");
+		return false;
+	}
+
+	icmp = (struct icmp6hdr *)((__u8 *)ctx_data(ctx) + l4_off);
+	if (icmp->icmp6_type != ICMP6_NS_MSG_TYPE) {
+		bpf_printk("KO 2");
+		return false;
+	}
+
+	/* Extract fields */
+#if 0
+	eth_load_saddr(ctx, &smac->addr[0], 0);
+	eth_load_daddr(ctx, &mac->addr[0], 0);
+	ipv6_load_saddr(ctx, l3_off, sip);
+	ipv6_load_daddr(ctx, l3_off, tip);
+#endif
+	bpf_printk("ACK ");
+
+	return true;
+}
+
+SEC("classifier")
+__description("agressive optimization due to uninitialized variable")
+#if __clang_major__ >= 21
+__failure __msg("last insn is bpf_unreachable, due to uninitialized var")
+#else
+__failure __msg("last insn is not an exit or jmp")
+#endif
+int classifier_uninit_var(struct __sk_buff *skb)
+{
+	struct ipv6hdr* ip6 = NULL;
+	union macaddr mac, smac;
+	union v6addr sip, tip;
+
+	bpf_printk("Start");
+	icmp6_ndisc_validate(skb, ip6, &mac, &smac, &sip, &tip);
+	bpf_printk("End");
+
+	return 0;
+}
+
+char _license[] SEC("license") = "GPL";
-- 
2.47.1






[Index of Archives]     [Linux Samsung SoC]     [Linux Rockchip SoC]     [Linux Actions SoC]     [Linux for Synopsys ARC Processors]     [Linux NFS]     [Linux NILFS]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]


  Powered by Linux