Thread (4 messages) flat view 4 messages, 2 authors, 1d ago
WARM1d

[PATCH] lsm: initialize the security blob for the initial namespaces

From: Stephen Smalley <stephen.smalley.work@gmail.com>
Date: 2026-09-16 19:48:48
Also in: selinux
Subsystem: security subsystem, the rest · Maintainers: Paul Moore, James Morris, "Serge E. Hallyn", Linus Torvalds

Commit f675d2e95569 ("lsm: add LSM blob and hooks for namespaces")
added a security blob to struct ns_common and allocates it from
__ns_common_init(). Most initial namespaces (init_user_ns,
init_uts_ns, init_pid_ns, init_mnt_ns, and, depending on
configuration, init_ipc_ns, init_cgroup_ns and init_time_ns) are
however set up statically via NS_COMMON_INIT() and never pass through
__ns_common_init(), so their ns_security pointer is left NULL for the
lifetime of the system. Any LSM that registers an lbs_ns blob and
dereferences its slice on one of those namespaces (for example from
the namespace_install hook when a task setns()'s back into an initial
namespace) will fault.

Allocate the blob and run the namespace_init hook for each of these
namespaces at the end of security_init(), after all ordered LSMs have
registered their hooks, so every LSM sees the initial namespaces the
same way it sees init_net (which is initialized at runtime via
ns_common_init(&init_net) from net_ns_init() immediately after
security_init()) and does not need to special-case them in its own
->init() callback.

Provide an init_ns_common_for_each() iterator in kernel/nscommon.c so
the list of statically-defined initial namespaces and their Kconfig
guards live next to the rest of the ns_common infrastructure. init_net
is intentionally excluded: pre-initializing it here would leak once
__ns_common_init() clears ns_security and re-allocates.

Fixes: f675d2e95569 ("lsm: add LSM blob and hooks for namespaces")
Signed-off-by: Stephen Smalley <stephen.smalley.work@gmail.com>
---
 include/linux/ns_common.h |  1 +
 kernel/nscommon.c         | 45 +++++++++++++++++++++++++++++++++++++++
 security/lsm_init.c       | 11 ++++++++++
 3 files changed, 57 insertions(+)
diff --git a/include/linux/ns_common.h b/include/linux/ns_common.h
index c8e227a3f9e2..742627000453 100644
--- a/include/linux/ns_common.h
+++ b/include/linux/ns_common.h
@@ -11,6 +11,7 @@
 bool is_current_namespace(struct ns_common *ns);
 int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_operations *ops, int inum);
 void __ns_common_free(struct ns_common *ns);
+int init_ns_common_for_each(int (*fn)(struct ns_common *ns));
 struct ns_common *__must_check ns_owner(struct ns_common *ns);
 
 static __always_inline bool is_ns_init_inum(const struct ns_common *ns)
diff --git a/kernel/nscommon.c b/kernel/nscommon.c
index e72426bba29a..4f3cba5518d7 100644
--- a/kernel/nscommon.c
+++ b/kernel/nscommon.c
@@ -1,11 +1,17 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /* Copyright (c) 2025 Christian Brauner <brauner@kernel.org> */
 
+#include <linux/cgroup.h>
+#include <linux/ipc_namespace.h>
+#include <linux/mnt_namespace.h>
 #include <linux/ns_common.h>
 #include <linux/nstree.h>
+#include <linux/pid_namespace.h>
 #include <linux/proc_ns.h>
 #include <linux/security.h>
+#include <linux/time_namespace.h>
 #include <linux/user_namespace.h>
+#include <linux/uts_namespace.h>
 #include <linux/vfsdebug.h>
 
 #ifdef CONFIG_DEBUG_VFS
@@ -101,6 +107,45 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope
 	return 0;
 }
 
+/**
+ * init_ns_common_for_each - iterate the statically-defined initial namespaces
+ * @fn: callback invoked with each initial ns_common
+ *
+ * Walk the initial namespaces that are set up via NS_COMMON_INIT() at compile
+ * time and therefore never pass through __ns_common_init(). The initial
+ * network namespace is intentionally excluded: it is initialized at runtime
+ * via ns_common_init(&init_net) from net_ns_init().
+ *
+ * Return: the first non-zero return from @fn, or 0.
+ */
+int __init init_ns_common_for_each(int (*fn)(struct ns_common *ns))
+{
+	struct ns_common *set[] = {
+		to_ns_common(&init_user_ns),
+		to_ns_common(&init_uts_ns),
+		to_ns_common(&init_pid_ns),
+		from_mnt_ns(&init_mnt_ns),
+#if defined(CONFIG_POSIX_MQUEUE) || defined(CONFIG_SYSVIPC)
+		to_ns_common(&init_ipc_ns),
+#endif
+#ifdef CONFIG_CGROUPS
+		to_ns_common(&init_cgroup_ns),
+#endif
+#ifdef CONFIG_TIME_NS
+		to_ns_common(&init_time_ns),
+#endif
+	};
+	unsigned int i;
+	int ret;
+
+	for (i = 0; i < ARRAY_SIZE(set); i++) {
+		ret = fn(set[i]);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
 void __ns_common_free(struct ns_common *ns)
 {
 	security_namespace_free(ns);
diff --git a/security/lsm_init.c b/security/lsm_init.c
index dbda7771013b..60e15215f6bd 100644
--- a/security/lsm_init.c
+++ b/security/lsm_init.c
@@ -7,6 +7,7 @@
 
 #include <linux/init.h>
 #include <linux/lsm_hooks.h>
+#include <linux/ns_common.h>
 
 #include "lsm.h"
 
@@ -490,6 +491,16 @@ int __init security_init(void)
 		lsm_init_single(*lsm);
 	}
 
+	/*
+	 * The initial namespaces are set up statically via NS_COMMON_INIT()
+	 * and never pass through __ns_common_init(), so allocate their
+	 * security blobs and run the namespace_init hooks now that all LSMs
+	 * are registered. init_net is not covered here; it is initialized
+	 * at runtime from net_ns_init() shortly after security_init().
+	 */
+	if (init_ns_common_for_each(security_namespace_init))
+		panic("initial LSM ns alloc failed\n");
+
 	return 0;
 }
 
-- 
2.55.0
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help