[PATCH 8/8] selftests/bpf: add a lifecycle test for the namespace LSM hooks
Christian Brauner
brauner at kernel.org
Mon Oct 26 09:57:16 UTC 2026
Track new uts, ipc and net namespaces from a bpf LSM by their namespace
id and check that:
- namespace_init already sees the id that NS_GET_ID reports
- namespace_init, namespace_install and namespace_free fire exactly once
for each of them
The test fails if the namespace_free hook isn't called or if the
namespace id is allocated after namespace_init.
Signed-off-by: Christian Brauner (Amutable) <brauner at kernel.org>
---
.../selftests/bpf/prog_tests/ns_lifecycle.c | 145 +++++++++++++++++++++
.../selftests/bpf/progs/test_ns_lifecycle.c | 88 +++++++++++++
2 files changed, 233 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/ns_lifecycle.c b/tools/testing/selftests/bpf/prog_tests/ns_lifecycle.c
new file mode 100644
index 000000000000..8ea4939efb23
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/ns_lifecycle.c
@@ -0,0 +1,145 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Exercise all namespace LSM hooks from a BPF LSM: unshare() new
+ * namespaces (namespace_init), setns() into them (namespace_install) and
+ * drop the last reference (namespace_free).
+ */
+
+#define _GNU_SOURCE
+#include <test_progs.h>
+#include <sched.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <linux/nsfs.h>
+#include "task_local_storage_helpers.h"
+#include "test_ns_lifecycle.skel.h"
+
+struct ns_stat {
+ __u32 ns_type;
+ __u32 init_count;
+ __u32 install_count;
+ __u32 free_count;
+};
+
+static const struct {
+ const char *path;
+ int type;
+} namespaces[] = {
+ { "/proc/self/ns/uts", CLONE_NEWUTS },
+ { "/proc/self/ns/ipc", CLONE_NEWIPC },
+ { "/proc/self/ns/net", CLONE_NEWNET },
+};
+
+#define NR_NS ARRAY_SIZE(namespaces)
+
+static bool all_freed(int map_fd, const __u64 *ns_id)
+{
+ struct ns_stat stat;
+ int i;
+
+ for (i = 0; i < NR_NS; i++) {
+ if (bpf_map_lookup_elem(map_fd, &ns_id[i], &stat) || !stat.free_count)
+ return false;
+ }
+ return true;
+}
+
+void test_ns_lifecycle(void)
+{
+ int orig_fd[NR_NS] = { -1, -1, -1 }, new_fd[NR_NS] = { -1, -1, -1 };
+ struct test_ns_lifecycle *skel = NULL;
+ int pidfd, map_fd, err, i, tries;
+ struct ns_stat stat;
+ __u64 ns_id[NR_NS];
+ struct stat st;
+
+ for (i = 0; i < NR_NS; i++) {
+ orig_fd[i] = open(namespaces[i].path, O_RDONLY | O_CLOEXEC);
+ if (!ASSERT_OK_FD(orig_fd[i], "open orig ns"))
+ goto close_fds;
+ }
+
+ pidfd = sys_pidfd_open(getpid(), 0);
+ if (!ASSERT_OK_FD(pidfd, "pidfd_open"))
+ goto close_fds;
+ err = fstat(pidfd, &st);
+ close(pidfd);
+ if (!ASSERT_OK(err, "fstat pidfd"))
+ goto close_fds;
+
+ skel = test_ns_lifecycle__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "skel open_and_load"))
+ goto close_fds;
+
+ err = test_ns_lifecycle__attach(skel);
+ if (!ASSERT_OK(err, "skel attach"))
+ goto destroy;
+
+ map_fd = bpf_map__fd(skel->maps.ns_lifecycle);
+ skel->bss->monitored_pidfd_ino = st.st_ino;
+
+ err = unshare(CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWNET);
+ if (!ASSERT_OK(err, "unshare"))
+ goto disarm;
+
+ for (i = 0; i < NR_NS; i++) {
+ new_fd[i] = open(namespaces[i].path, O_RDONLY | O_CLOEXEC);
+ if (!ASSERT_OK_FD(new_fd[i], "open new ns"))
+ goto restore;
+ if (!ASSERT_OK(ioctl(new_fd[i], NS_GET_ID, &ns_id[i]), "NS_GET_ID"))
+ goto restore;
+ }
+
+ /* Leave the new namespaces and install them again. */
+ for (i = 0; i < NR_NS; i++) {
+ if (!ASSERT_OK(setns(orig_fd[i], namespaces[i].type), "setns orig"))
+ goto restore;
+ if (!ASSERT_OK(setns(new_fd[i], namespaces[i].type), "setns new"))
+ goto restore;
+ if (!ASSERT_OK(setns(orig_fd[i], namespaces[i].type), "setns orig again"))
+ goto restore;
+ }
+
+ skel->bss->monitored_pidfd_ino = 0;
+
+ /* Drop the last references. Network namespaces are freed asynchronously. */
+ for (i = 0; i < NR_NS; i++) {
+ close(new_fd[i]);
+ new_fd[i] = -1;
+ }
+
+ for (tries = 0; tries < 1000 && !all_freed(map_fd, ns_id); tries++) {
+ kern_sync_rcu();
+ usleep(10000);
+ }
+
+ for (i = 0; i < NR_NS; i++) {
+ /* namespace_init must already have seen the id. */
+ if (!ASSERT_OK(bpf_map_lookup_elem(map_fd, &ns_id[i], &stat), "lookup ns_stat"))
+ continue;
+
+ ASSERT_EQ(stat.ns_type, namespaces[i].type, "ns_type");
+ ASSERT_EQ(stat.init_count, 1, "init_count");
+ ASSERT_EQ(stat.install_count, 1, "install_count");
+ ASSERT_EQ(stat.free_count, 1, "free_count");
+ }
+ goto destroy;
+
+restore:
+ for (i = 0; i < NR_NS; i++)
+ setns(orig_fd[i], namespaces[i].type);
+disarm:
+ skel->bss->monitored_pidfd_ino = 0;
+destroy:
+ test_ns_lifecycle__destroy(skel);
+close_fds:
+ for (i = 0; i < NR_NS; i++) {
+ if (new_fd[i] >= 0)
+ close(new_fd[i]);
+ if (orig_fd[i] >= 0)
+ close(orig_fd[i]);
+ }
+}
diff --git a/tools/testing/selftests/bpf/progs/test_ns_lifecycle.c b/tools/testing/selftests/bpf/progs/test_ns_lifecycle.c
new file mode 100644
index 000000000000..88c5d40ba27a
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/test_ns_lifecycle.c
@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include "vmlinux.h"
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+struct ns_stat {
+ __u32 ns_type;
+ __u32 init_count;
+ __u32 install_count;
+ __u32 free_count;
+};
+
+/* Unlike inode numbers namespace ids are never recycled. */
+struct {
+ __uint(type, BPF_MAP_TYPE_HASH);
+ __uint(max_entries, 16);
+ __type(key, __u64);
+ __type(value, struct ns_stat);
+} ns_lifecycle SEC(".maps");
+
+__u64 monitored_pidfd_ino = 0;
+
+char _license[] SEC("license") = "GPL";
+
+static bool is_monitored(void)
+{
+ struct task_struct *task;
+
+ if (!monitored_pidfd_ino)
+ return false;
+
+ task = bpf_get_current_task_btf();
+ return task->thread_pid->ino == monitored_pidfd_ino;
+}
+
+SEC("lsm/namespace_init")
+int BPF_PROG(ns_init, struct ns_common *ns, int ret)
+{
+ struct ns_stat new = {}, *stat;
+ __u64 ns_id;
+
+ if (ret)
+ return ret;
+
+ if (!is_monitored())
+ return 0;
+
+ ns_id = ns->ns_id;
+ new.ns_type = ns->ns_type;
+ bpf_map_update_elem(&ns_lifecycle, &ns_id, &new, BPF_NOEXIST);
+ stat = bpf_map_lookup_elem(&ns_lifecycle, &ns_id);
+ if (stat)
+ __sync_fetch_and_add(&stat->init_count, 1);
+ return 0;
+}
+
+SEC("lsm/namespace_install")
+int BPF_PROG(ns_install, const struct nsset *nsset, struct ns_common *ns, int ret)
+{
+ struct ns_stat *stat;
+ __u64 ns_id;
+
+ if (ret)
+ return ret;
+
+ if (!is_monitored())
+ return 0;
+
+ ns_id = ns->ns_id;
+ stat = bpf_map_lookup_elem(&ns_lifecycle, &ns_id);
+ if (stat)
+ __sync_fetch_and_add(&stat->install_count, 1);
+ return 0;
+}
+
+SEC("lsm/namespace_free")
+int BPF_PROG(ns_free, struct ns_common *ns)
+{
+ struct ns_stat *stat;
+ __u64 ns_id;
+
+ ns_id = ns->ns_id;
+ stat = bpf_map_lookup_elem(&ns_lifecycle, &ns_id);
+ if (stat)
+ __sync_fetch_and_add(&stat->free_count, 1);
+ return 0;
+}
--
2.53.0
More information about the Linux-security-module-archive
mailing list