diff --git a/src/nsresourced/nsresourced-manager.c b/src/nsresourced/nsresourced-manager.c index 41436e5552..daa32e4e1e 100644 --- a/src/nsresourced/nsresourced-manager.c +++ b/src/nsresourced/nsresourced-manager.c @@ -135,6 +135,16 @@ int manager_new(Manager **ret) { return 0; } +#if HAVE_VMLINUX_H +static void manager_unload_bpf(Manager *m) { + assert(m); + + m->userns_restrict_bpf_ring_buffer_event_source = sd_event_source_disable_unref(m->userns_restrict_bpf_ring_buffer_event_source); + m->userns_restrict_bpf_ring_buffer = bpf_ring_buffer_free(m->userns_restrict_bpf_ring_buffer); + m->userns_restrict_bpf = userns_restrict_bpf_free(m->userns_restrict_bpf); +} +#endif + Manager* manager_free(Manager *m) { if (!m) return NULL; @@ -147,9 +157,7 @@ Manager* manager_free(Manager *m) { safe_close(m->listen_fd); #if HAVE_VMLINUX_H - sd_event_source_disable_unref(m->userns_restrict_bpf_ring_buffer_event_source); - bpf_ring_buffer_free(m->userns_restrict_bpf_ring_buffer); - userns_restrict_bpf_free(m->userns_restrict_bpf); + manager_unload_bpf(m); #endif safe_close(m->registry_fd); @@ -651,19 +659,21 @@ static int manager_setup_bpf(Manager *m) { /* Puts the programs loaded by manager_setup_bpf() in effect. Deliberately separate, so that the maps are * already seeded from the registry by the time the first hook runs. */ -static int manager_attach_bpf(Manager *m) { +static void manager_attach_bpf(Manager *m) { int r; assert(m); if (!m->userns_restrict_bpf) /* Setup failed earlier, and we already told the user. */ - return 0; + return; + /* Some kernels can load BPF-LSM programs but not attach them, e.g. arm64 before 7.2. Handle this + * like a failed setup. */ r = userns_restrict_attach(m->userns_restrict_bpf, /* pin= */ true); - if (r < 0) - return log_error_errno(r, "Failed to attach BPF programs: %m"); - - return 0; + if (r < 0) { + log_notice_errno(r, "Proceeding with user namespace interfaces disabled."); + manager_unload_bpf(m); + } } #else static int manager_setup_bpf(Manager *m) { @@ -671,8 +681,7 @@ static int manager_setup_bpf(Manager *m) { return 0; } -static int manager_attach_bpf(Manager *m) { - return 0; +static void manager_attach_bpf(Manager *m) { } #endif @@ -759,9 +768,7 @@ int manager_startup(Manager *m) { /* The maps now describe every namespace we are responsible for, so the policy can go into * effect. Until this point the previous version's programs, if any, were still attached and * enforcing, so no namespace was ever left unguarded. */ - r = manager_attach_bpf(m); - if (r < 0) - return r; + manager_attach_bpf(m); r = manager_make_listen_socket(m); if (r < 0) diff --git a/src/nsresourced/test-userns-restrict.c b/src/nsresourced/test-userns-restrict.c index cbb928de20..d1377a366d 100644 --- a/src/nsresourced/test-userns-restrict.c +++ b/src/nsresourced/test-userns-restrict.c @@ -64,7 +64,10 @@ static int intro(void) { return log_tests_skipped("Lacking privileges"); ASSERT_OK(r); - ASSERT_OK(userns_restrict_attach(bpf_obj, /* pin= */ false)); + r = userns_restrict_attach(bpf_obj, /* pin= */ false); + if (ERRNO_IS_NOT_SUPPORTED(r)) + return log_tests_skipped("LSM-BPF programs cannot be attached"); + ASSERT_OK(r); return 0; } diff --git a/src/nsresourced/userns-restrict.c b/src/nsresourced/userns-restrict.c index 0f6f021e1d..bb375f3ba7 100644 --- a/src/nsresourced/userns-restrict.c +++ b/src/nsresourced/userns-restrict.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #if HAVE_VMLINUX_H #include "userns-restrict-skel.h" @@ -128,6 +129,18 @@ int userns_restrict_install( #endif } +#if HAVE_VMLINUX_H +static char** unpin_links_and_free(char **paths) { + STRV_FOREACH(p, paths) + if (unlink(*p) < 0 && errno != ENOENT) + log_debug_errno(errno, "Failed to unpin '%s', ignoring: %m", *p); + + return strv_free(paths); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(char**, unpin_links_and_free); +#endif + int userns_restrict_attach(struct userns_restrict_bpf *obj, bool pin) { #if HAVE_VMLINUX_H @@ -135,6 +148,11 @@ int userns_restrict_attach(struct userns_restrict_bpf *obj, bool pin) { assert(obj); + /* If a program fails to attach, the programs attached before it stay attached, because their links + * are pinned. The caller then drops its BPF state and no longer reaps dead namespaces from the maps. + * Remove the pins created here, so that destroying the object detaches the programs. */ + _cleanup_(unpin_links_and_freep) char **pinned = NULL; + for (int i = 0; i < obj->skeleton->prog_cnt; i++) { _cleanup_(bpf_link_freep) struct bpf_link *link = NULL; struct bpf_prog_skeleton *ps = obj->skeleton->progs + i; @@ -177,11 +195,18 @@ int userns_restrict_attach(struct userns_restrict_bpf *obj, bool pin) { r = sym_bpf_link__pin(link, fn); if (r < 0) return log_error_errno(r, "Failed to pin LSM attachment: %m"); + + if (strv_extend(&pinned, fn) < 0) { + (void) unlink(fn); + return log_oom(); + } } *ps->link = TAKE_PTR(link); } + pinned = strv_free(pinned); + /* Only now that our own programs enforce the policy, drop the ones the previous version left * pinned. Removing their links detaches them, so doing this any earlier would hand the namespaces * that predate an upgrade a stretch with no policy attached at all. */