From: Yonghong Song <yonghong.song@linux.dev>
To: bpf@vger.kernel.org
Cc: Alexei Starovoitov <ast@kernel.org>,
Andrii Nakryiko <andrii@kernel.org>,
Daniel Borkmann <daniel@iogearbox.net>,
kernel-team@fb.com, Martin KaFai Lau <martin.lau@kernel.org>
Subject: [PATCH bpf-next 2/2] selftests/bpf: Add a test with bpf_unreachable() kfunc
Date: Sun, 11 May 2025 11:27:49 -0700 [thread overview]
Message-ID: <20250511182749.1807380-1-yonghong.song@linux.dev> (raw)
In-Reply-To: <20250511182744.1806792-1-yonghong.song@linux.dev>
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@linux.dev>
---
.../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
next prev parent reply other threads:[~2025-05-11 18:28 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-05-11 18:27 [PATCH bpf-next 1/2] bpf: Warn with new bpf_unreachable() kfunc maybe due to uninitialized var Yonghong Song
2025-05-11 18:27 ` Yonghong Song [this message]
2025-05-12 0:30 ` Alexei Starovoitov
2025-05-13 5:49 ` Yonghong Song
2025-05-13 14:54 ` Alexei Starovoitov
2025-05-13 18:05 ` Yonghong Song
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250511182749.1807380-1-yonghong.song@linux.dev \
--to=yonghong.song@linux.dev \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=kernel-team@fb.com \
--cc=martin.lau@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.