From 4acbcd330278b3fd61a313ee47c0306664d4c552 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 1 Oct 2026 11:31:46 +0200 Subject: [PATCH] nsresourced: keep running if the BPF-LSM programs cannot be attached Since 7eb016d60b ("nsresourced: restore the BPF-LSM policy from the registry on startup"), the programs are loaded in manager_setup_bpf() but only attached later in manager_attach_bpf(). Before that, a failed attach made the whole setup fail, and nsresourced proceeded with the user namespace interfaces disabled. Now the same failure is fatal, and nsresourced exits on kernels that can load BPF-LSM programs but not attach them, such as arm64 before 7.2. Handle a failed attach like a failed setup again: log it, drop the BPF state, and continue with the user namespace interfaces disabled. When a program fails to attach, the programs attached before it stay attached, because userns_restrict_attach() has already pinned their links. The programs keep enforcing the policy. Without the ring buffer reader, nsresourced never reaps dead namespaces from the maps. Remove the pins created by the failed attach, so that dropping the BPF state detaches the programs. test-userns-restrict fails on the same kernels, because it asserts that the attach succeeds. Skip it instead. Fixes: #43964 (cherry picked from commit 4446b9bb51c9900f22d7ba50379fa96dcbfb58f2) --- src/nsresourced/nsresourced-manager.c | 35 +++++++++++++++----------- src/nsresourced/test-userns-restrict.c | 5 +++- src/nsresourced/userns-restrict.c | 25 ++++++++++++++++++ 3 files changed, 50 insertions(+), 15 deletions(-) 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. */