From: Yu Watanabe Date: Wed, 16 Apr 2025 17:05:09 +0000 (+0900) Subject: bpf-program: introduce bpf_program_supported() helper function X-Git-Tag: v258-rc1~653^2~4 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=71be1f3875fc1b4a2a16779ab35f27ac5a02e212;p=thirdparty%2Fsystemd.git bpf-program: introduce bpf_program_supported() helper function It checks if the kernel is built with CONFIG_CGROUP_BPF. It is currently unused, but will be used later. --- diff --git a/src/shared/bpf-program.c b/src/shared/bpf-program.c index 96ce5e9006c..83af54ec681 100644 --- a/src/shared/bpf-program.c +++ b/src/shared/bpf-program.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include #include #include @@ -42,6 +43,52 @@ DEFINE_STRING_TABLE_LOOKUP(bpf_cgroup_attach_type, int); DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(bpf_program_hash_ops, void, trivial_hash_func, trivial_compare_func, bpf_program_free); +int bpf_program_supported(void) { + static int cached = 0; + + if (cached != 0) + return cached; + + /* Currently, we only use the following three types: + * - BPF_PROG_TYPE_CGROUP_SKB, supported since kernel v4.10 (0e33661de493db325435d565a4a722120ae4cbf3), + * - BPF_PROG_TYPE_CGROUP_DEVICE, supported since kernel v4.15 (ebc614f687369f9df99828572b1d85a7c2de3d92), + * - BPF_PROG_TYPE_CGROUP_SOCK_ADDR, supported since kernel v4.17 (4fbac77d2d092b475dda9eea66da674369665427). + * Hence, as our baseline on the kernel is v5.4, it is not necessary to check if we can create BPF + * programs of hthese types. + * + * However, unfortunately the kernel allows us to create BPF_PROG_TYPE_CGROUP_SKB (maybe also other types) + * programs even when CONFIG_CGROUP_BPF is turned off at kernel compilation time. This sucks of course: + * why does it allow us to create a cgroup BPF program if we can't do a thing with it later? + * + * We detect this case by issuing the BPF_PROG_DETACH bpf() call with invalid file descriptors: if + * CONFIG_CGROUP_BPF is turned off, then the call will fail early with EINVAL. If it is turned on the + * parameters are validated however, and that'll fail with EBADF then. + * + * The check seems also important when we are running with sanitizers. With sanitizers (at least with + * LLVM v20), the following check and other bpf() calls fails even if the kernel supports BPF. To + * avoid unexpected fail when running with sanitizers, let's explicitly check if bpf() syscall works. */ + + /* Clang and GCC (>=15) do not 0-pad with structured initialization, causing the kernel to reject the + * bpf_attr as invalid. See: https://github.com/torvalds/linux/blob/v5.9/kernel/bpf/syscall.c#L65 + * Hence, we cannot use structured initialization here, and need to clear the structure with zero + * explicitly before use. */ + union bpf_attr attr; + zero(attr); + attr.attach_type = BPF_CGROUP_INET_EGRESS; /* since kernel v4.10 (0e33661de493db325435d565a4a722120ae4cbf3) */ + attr.target_fd = -EBADF; + attr.attach_bpf_fd = -EBADF; + + if (bpf(BPF_PROG_DETACH, &attr, sizeof(attr)) < 0) { + if (errno == EBADF) /* YAY! */ + return cached = true; + + return cached = log_debug_errno(errno, "Didn't get EBADF from invalid BPF_PROG_DETACH call: %m"); + } + + return cached = log_debug_errno(SYNTHETIC_ERRNO(EBADE), + "Wut? Kernel accepted our invalid BPF_PROG_DETACH call? Something is weird, assuming BPF is broken and hence not supported."); +} + BPFProgram *bpf_program_free(BPFProgram *p) { if (!p) return NULL; diff --git a/src/shared/bpf-program.h b/src/shared/bpf-program.h index 904f2b941f5..e820ed8196d 100644 --- a/src/shared/bpf-program.h +++ b/src/shared/bpf-program.h @@ -33,6 +33,8 @@ struct BPFProgram { uint32_t attached_flags; }; +int bpf_program_supported(void); + int bpf_program_new(uint32_t prog_type, const char *prog_name, BPFProgram **ret); int bpf_program_new_from_bpffs_path(const char *path, BPFProgram **ret); BPFProgram *bpf_program_free(BPFProgram *p);