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