]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
bpf-program: introduce bpf_program_supported() helper function
authorYu Watanabe <watanabe.yu+github@gmail.com>
Wed, 16 Apr 2025 17:05:09 +0000 (02:05 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Fri, 9 May 2025 15:17:52 +0000 (00:17 +0900)
It checks if the kernel is built with CONFIG_CGROUP_BPF.
It is currently unused, but will be used later.

src/shared/bpf-program.c
src/shared/bpf-program.h

index 96ce5e9006c69bf955029ce067b5edc0f993d73b..83af54ec6816e49c7277fd590cb64da0fdf8e54b 100644 (file)
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
 #include <fcntl.h>
+#include <linux/bpf_insn.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <unistd.h>
@@ -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;
index 904f2b941f5720af261360aa1810253ba7d97134..e820ed8196dc33305ef2bc53512fc12b16481e4b 100644 (file)
@@ -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);