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)
This commit is contained in:
committed by
Luca Boccassi
parent
58befb6079
commit
4acbcd3302
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#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. */
|
||||
|
||||
Reference in New Issue
Block a user