From 2ae7ee58fa03340f767821298475d8845bda0b2c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 16 Feb 2018 11:55:33 +0100 Subject: [PATCH] bpf: beef up bpf detection, check if BPF_F_ALLOW_MULTI is supported This improves the BPF/cgroup detection logic, and looks whether BPF_ALLOW_MULTI is supported. This flag allows execution of multiple BPF filters in a recursive fashion for a whole cgroup tree. It enables us to properly report IP accounting for slice units, as well as delegation of BPF support to units without breaking our own IP accounting. --- src/core/bpf-firewall.c | 60 +++++++++++++++++++++++++++--------- src/core/bpf-firewall.h | 6 ++++ src/core/dbus-cgroup.c | 2 +- src/core/ip-address-access.c | 2 +- src/core/socket.c | 4 +-- src/test/test-bpf.c | 7 ++++- 6 files changed, 62 insertions(+), 19 deletions(-) diff --git a/src/core/bpf-firewall.c b/src/core/bpf-firewall.c index c5cda83006..e717be2ce7 100644 --- a/src/core/bpf-firewall.c +++ b/src/core/bpf-firewall.c @@ -493,7 +493,7 @@ int bpf_firewall_compile(Unit *u) { r = bpf_firewall_supported(); if (r < 0) return r; - if (r == 0) { + if (r == BPF_FIREWALL_UNSUPPORTED) { log_debug("BPF firewalling not supported on this manager, proceeding without."); return -EOPNOTSUPP; } @@ -555,7 +555,7 @@ int bpf_firewall_install(Unit *u) { r = bpf_firewall_supported(); if (r < 0) return r; - if (r == 0) { + if (r == BPF_FIREWALL_UNSUPPORTED) { log_debug("BPF firewalling not supported on this manager, proceeding without."); return -EOPNOTSUPP; } @@ -667,7 +667,7 @@ int bpf_firewall_supported(void) { if (geteuid() != 0) { log_debug("Not enough privileges, BPF firewalling is not supported."); - return supported = false; + return supported = BPF_FIREWALL_UNSUPPORTED; } r = cg_unified_controller(SYSTEMD_CGROUP_CONTROLLER); @@ -675,7 +675,7 @@ int bpf_firewall_supported(void) { return log_error_errno(r, "Can't determine whether the unified hierarchy is used: %m"); if (r == 0) { log_debug("Not running with unified cgroups, BPF firewalling is not supported."); - return supported = false; + return supported = BPF_FIREWALL_UNSUPPORTED; } fd = bpf_map_new(BPF_MAP_TYPE_LPM_TRIE, @@ -685,26 +685,26 @@ int bpf_firewall_supported(void) { BPF_F_NO_PREALLOC); if (fd < 0) { log_debug_errno(r, "Can't allocate BPF LPM TRIE map, BPF firewalling is not supported: %m"); - return supported = false; + return supported = BPF_FIREWALL_UNSUPPORTED; } safe_close(fd); if (bpf_program_new(BPF_PROG_TYPE_CGROUP_SKB, &program) < 0) { log_debug_errno(r, "Can't allocate CGROUP SKB BPF program, BPF firewalling is not supported: %m"); - return supported = false; + return supported = BPF_FIREWALL_UNSUPPORTED; } r = bpf_program_add_instructions(program, trivial, ELEMENTSOF(trivial)); if (r < 0) { log_debug_errno(r, "Can't add trivial instructions to CGROUP SKB BPF program, BPF firewalling is not supported: %m"); - return supported = false; + return supported = BPF_FIREWALL_UNSUPPORTED; } r = bpf_program_load_kernel(program, NULL, 0); if (r < 0) { log_debug_errno(r, "Can't load kernel CGROUP SKB BPF program, BPF firewalling is not supported: %m"); - return supported = false; + return supported = BPF_FIREWALL_UNSUPPORTED; } /* Unfortunately the kernel allows us to create BPF_PROG_TYPE_CGROUP_SKB programs even when CONFIG_CGROUP_BPF @@ -723,12 +723,44 @@ int bpf_firewall_supported(void) { r = bpf(BPF_PROG_ATTACH, &attr, sizeof(attr)); if (r < 0) { - if (errno == EBADF) /* YAY! */ - return supported = true; + if (errno != EBADF) { + log_debug_errno(errno, "Didn't get EBADF from BPF_PROG_ATTACH, BPF firewalling is not supported: %m"); + return supported = BPF_FIREWALL_UNSUPPORTED; + } - log_debug_errno(errno, "Didn't get EBADF from BPF_PROG_ATTACH, BPF firewalling is not supported: %m"); - } else - log_debug("Wut? kernel accepted our invalid BPF_PROG_ATTACH call? Something is weird, assuming BPF firewalling is broken and hence not supported."); + /* YAY! */ + } else { + log_debug("Wut? Kernel accepted our invalid BPF_PROG_ATTACH call? Something is weird, assuming BPF firewalling is broken and hence not supported."); + return supported = BPF_FIREWALL_UNSUPPORTED; + } - return supported = false; + /* So now we know that the BPF program is generally available, let's see if BPF_F_ALLOW_MULTI is also supported + * (which was added in kernel 4.15). We use a similar logic as before, but this time we use + * BPF_F_ALLOW_MULTI. Since the flags are checked early in the system call we'll get EINVAL if it's not + * supported, and EBADF as before if it is available. */ + + attr = (union bpf_attr) { + .attach_type = BPF_CGROUP_INET_EGRESS, + .target_fd = -1, + .attach_bpf_fd = -1, + .attach_flags = BPF_F_ALLOW_MULTI, + }; + + r = bpf(BPF_PROG_ATTACH, &attr, sizeof(attr)); + if (r < 0) { + if (errno == EBADF) { + log_debug_errno(errno, "Got EBADF when using BPF_F_ALLOW_MULTI, which indicates it is supported. Yay!"); + return supported = BPF_FIREWALL_SUPPORTED_WITH_MULTI; + } + + if (errno == EINVAL) + log_debug_errno(errno, "Got EINVAL error when using BPF_F_ALLOW_MULTI, which indicates it's not supported."); + else + log_debug_errno(errno, "Got unexpected error when using BPF_F_ALLOW_MULTI, assuming it's not supported: %m"); + + return supported = BPF_FIREWALL_SUPPORTED; + } else { + log_debug("Wut? Kernel accepted our invalid BPF_PROG_ATTACH+BPF_F_ALLOW_MULTI call? Something is weird, assuming BPF firewalling is broken and hence not supported."); + return supported = BPF_FIREWALL_UNSUPPORTED; + } } diff --git a/src/core/bpf-firewall.h b/src/core/bpf-firewall.h index 37a1f2e003..a0658e3b86 100644 --- a/src/core/bpf-firewall.h +++ b/src/core/bpf-firewall.h @@ -24,6 +24,12 @@ #include "unit.h" +enum { + BPF_FIREWALL_UNSUPPORTED = 0, + BPF_FIREWALL_SUPPORTED = 1, + BPF_FIREWALL_SUPPORTED_WITH_MULTI = 2, +}; + int bpf_firewall_supported(void); int bpf_firewall_compile(Unit *u); diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c index 30456fa9f5..f480664613 100644 --- a/src/core/dbus-cgroup.c +++ b/src/core/dbus-cgroup.c @@ -1167,7 +1167,7 @@ int bus_cgroup_set_property( r = bpf_firewall_supported(); if (r < 0) return r; - if (r == 0) { + if (r == BPF_FIREWALL_UNSUPPORTED) { static bool warned = false; log_full(warned ? LOG_DEBUG : LOG_WARNING, diff --git a/src/core/ip-address-access.c b/src/core/ip-address-access.c index 08bd4c0bce..f10138c6de 100644 --- a/src/core/ip-address-access.c +++ b/src/core/ip-address-access.c @@ -156,7 +156,7 @@ int config_parse_ip_address_access( r = bpf_firewall_supported(); if (r < 0) return r; - if (r == 0) { + if (r == BPF_FIREWALL_UNSUPPORTED) { static bool warned = false; log_full(warned ? LOG_DEBUG : LOG_WARNING, diff --git a/src/core/socket.c b/src/core/socket.c index 1a57cf0e11..41988788b8 100644 --- a/src/core/socket.c +++ b/src/core/socket.c @@ -1521,7 +1521,7 @@ static int socket_address_listen_in_cgroup( r = bpf_firewall_supported(); if (r < 0) return r; - if (r == 0) /* If BPF firewalling isn't supported anyway — there's no point in this forking complexity */ + if (r == BPF_FIREWALL_UNSUPPORTED) /* If BPF firewalling isn't supported anyway — there's no point in this forking complexity */ goto shortcut; if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, pair) < 0) @@ -2865,7 +2865,7 @@ static int socket_accept_in_cgroup(Socket *s, SocketPort *p, int fd) { r = bpf_firewall_supported(); if (r < 0) return r; - if (r == 0) + if (r == BPF_FIREWALL_UNSUPPORTED) goto shortcut; if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, pair) < 0) diff --git a/src/test/test-bpf.c b/src/test/test-bpf.c index 361cf100be..6ca2be41b0 100644 --- a/src/test/test-bpf.c +++ b/src/test/test-bpf.c @@ -71,12 +71,17 @@ int main(int argc, char *argv[]) { } r = bpf_firewall_supported(); - if (r == 0) { + if (r == BPF_FIREWALL_UNSUPPORTED) { log_notice("BPF firewalling not supported, skipping"); return EXIT_TEST_SKIP; } assert_se(r > 0); + if (r == BPF_FIREWALL_SUPPORTED_WITH_MULTI) + log_notice("BPF firewalling with BPF_F_ALLOW_MULTI supported. Yay!"); + else + log_notice("BPF firewalling (though without BPF_F_ALLOW_MULTI) supported. Good."); + r = bpf_program_load_kernel(p, log_buf, ELEMENTSOF(log_buf)); assert(r >= 0);