Re: [PATCH] lsm: initialize the security blob for the initial namespaces
From: Stephen Smalley <stephen.smalley.work@gmail.com>
Date: 2026-09-18 13:55:23
Also in:
selinux
On Fri, Sep 18, 2026 at 8:55 AM Stephen Smalley [off-list ref] wrote:
On Fri, Sep 18, 2026 at 3:28 AM Christian Brauner [off-list ref] wrote:quoted
On Wed, Sep 16, 2026 at 03:48:24PM -0400, Stephen Smalley wrote:quoted
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");Wouldn't it be nicer to turn this around and call security_namespace_init from the ns common code and avoid the callback? Especially since there's only one.Good idea. I'll send a v2 that does this.
The v2 patch can be found at https://lore.kernel.org/linux-security-module/20260918134422.17053-2-stephen.smalley.work@gmail.com/T/#u (local)