lsm: add LSM blob and hooks for namespaces

All namespace types now share the same ns_common infrastructure. Extend
this to include a security blob so LSMs can start managing namespaces
uniformly without having to add one-off hooks or security fields to
every individual namespace type.

Add a ns_security pointer to ns_common and the corresponding lbs_ns blob
size to lsm_blob_sizes. Allocation and freeing hooks are called from the
common __ns_common_init() and __ns_common_free() paths so every
namespace type gets covered in one go. All information about the
namespace type and the appropriate casting helpers to get at the
containing namespace are available via ns_common making it
straightforward for LSMs to differentiate when they need to.

A namespace_install hook is called from validate_ns() during setns(2)
giving LSMs a chance to enforce policy on namespace transitions.  The
LSM check runs before ns->ops->install() so the security module can deny
the operation before any type-specific installation effects.

Individual namespace types can still have their own specialized security
hooks when needed. This is just the common baseline that makes it easy
to track and manage namespaces from the security side without requiring
every namespace type to reinvent the wheel.

Cc: Günther Noack <gnoack@google.com>
Cc: Paul Moore <paul@paul-moore.com>
Cc: Serge E. Hallyn <serge@hallyn.com>
Signed-off-by: Christian Brauner <brauner@kernel.org>
Link: https://lore.kernel.org/r/20260216-work-security-namespace-v1-1-075c28758e1f@kernel.org
Signed-off-by: Mickaël Salaün <mic@digikod.net>
[PM: subject tweak]
Signed-off-by: Paul Moore <paul@paul-moore.com>
This commit is contained in:
Christian Brauner 2026-07-26 18:13:47 +02:00 committed by Paul Moore
parent 4f238e6e1c
commit f675d2e955
8 changed files with 121 additions and 0 deletions

View File

@ -265,6 +265,9 @@ LSM_HOOK(int, -ENOSYS, task_prctl, int option, unsigned long arg2,
LSM_HOOK(void, LSM_RET_VOID, task_to_inode, struct task_struct *p,
struct inode *inode)
LSM_HOOK(int, 0, userns_create, const struct cred *cred)
LSM_HOOK(int, 0, namespace_init, struct ns_common *ns)
LSM_HOOK(void, LSM_RET_VOID, namespace_free, struct ns_common *ns)
LSM_HOOK(int, 0, namespace_install, const struct nsset *nsset, struct ns_common *ns)
LSM_HOOK(int, 0, ipc_permission, struct kern_ipc_perm *ipcp, short flag)
LSM_HOOK(void, LSM_RET_VOID, ipc_getlsmprop, struct kern_ipc_perm *ipcp,
struct lsm_prop *prop)

View File

@ -112,6 +112,7 @@ struct lsm_blob_sizes {
unsigned int lbs_ipc;
unsigned int lbs_key;
unsigned int lbs_msg_msg;
unsigned int lbs_ns;
unsigned int lbs_perf_event;
unsigned int lbs_task;
unsigned int lbs_xattr_count; /* num xattr slots in new_xattrs array */

View File

@ -116,6 +116,9 @@ struct ns_common {
struct dentry *stashed;
const struct proc_ns_operations *ops;
unsigned int inum;
#ifdef CONFIG_SECURITY
void *ns_security;
#endif
union {
struct ns_tree;
struct rcu_head ns_rcu;

View File

@ -67,6 +67,7 @@ enum fs_value_type;
struct watch;
struct watch_notification;
struct lsm_ctx;
struct nsset;
/* Default (no) options for the capable function */
#define CAP_OPT_NONE 0x0
@ -80,6 +81,7 @@ struct lsm_ctx;
struct ctl_table;
struct audit_krule;
struct ns_common;
struct user_namespace;
struct timezone;
@ -540,6 +542,9 @@ int security_task_prctl(int option, unsigned long arg2, unsigned long arg3,
unsigned long arg4, unsigned long arg5);
void security_task_to_inode(struct task_struct *p, struct inode *inode);
int security_create_user_ns(const struct cred *cred);
int security_namespace_init(struct ns_common *ns);
void security_namespace_free(struct ns_common *ns);
int security_namespace_install(const struct nsset *nsset, struct ns_common *ns);
int security_ipc_permission(struct kern_ipc_perm *ipcp, short flag);
void security_ipc_getlsmprop(struct kern_ipc_perm *ipcp, struct lsm_prop *prop);
int security_msg_msg_alloc(struct msg_msg *msg);
@ -1431,6 +1436,21 @@ static inline int security_create_user_ns(const struct cred *cred)
return 0;
}
static inline int security_namespace_init(struct ns_common *ns)
{
return 0;
}
static inline void security_namespace_free(struct ns_common *ns)
{
}
static inline int security_namespace_install(const struct nsset *nsset,
struct ns_common *ns)
{
return 0;
}
static inline int security_ipc_permission(struct kern_ipc_perm *ipcp,
short flag)
{

View File

@ -4,6 +4,7 @@
#include <linux/ns_common.h>
#include <linux/nstree.h>
#include <linux/proc_ns.h>
#include <linux/security.h>
#include <linux/user_namespace.h>
#include <linux/vfsdebug.h>
@ -59,6 +60,9 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope
refcount_set(&ns->__ns_ref, 1);
ns->stashed = NULL;
#ifdef CONFIG_SECURITY
ns->ns_security = NULL;
#endif
ns->ops = ops;
ns->ns_id = 0;
ns->ns_type = ns_type;
@ -77,6 +81,14 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope
ret = proc_alloc_inum(&ns->inum);
if (ret)
return ret;
ret = security_namespace_init(ns);
if (ret) {
if (!inum)
proc_free_inum(ns->inum);
return ret;
}
/*
* Tree ref starts at 0. It's incremented when namespace enters
* active use (installed in nsproxy) and decremented when all
@ -91,6 +103,8 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope
void __ns_common_free(struct ns_common *ns)
{
security_namespace_free(ns);
if (ns->inum > MNT_NS_INO_SPECIAL_MAX)
proc_free_inum(ns->inum);
}

View File

@ -385,6 +385,12 @@ static int prepare_nsset(unsigned flags, struct nsset *nsset)
static inline int validate_ns(struct nsset *nsset, struct ns_common *ns)
{
int ret;
ret = security_namespace_install(nsset, ns);
if (ret)
return ret;
return ns->ops->install(nsset, ns);
}

View File

@ -303,6 +303,7 @@ static void __init lsm_prepare(struct lsm_info *lsm)
lsm_blob_size_update(&blobs->lbs_ipc, &blob_sizes.lbs_ipc);
lsm_blob_size_update(&blobs->lbs_key, &blob_sizes.lbs_key);
lsm_blob_size_update(&blobs->lbs_msg_msg, &blob_sizes.lbs_msg_msg);
lsm_blob_size_update(&blobs->lbs_ns, &blob_sizes.lbs_ns);
lsm_blob_size_update(&blobs->lbs_perf_event,
&blob_sizes.lbs_perf_event);
lsm_blob_size_update(&blobs->lbs_sock, &blob_sizes.lbs_sock);
@ -450,6 +451,7 @@ int __init security_init(void)
lsm_pr("blob(ipc) size %d\n", blob_sizes.lbs_ipc);
lsm_pr("blob(key) size %d\n", blob_sizes.lbs_key);
lsm_pr("blob(msg_msg)_size %d\n", blob_sizes.lbs_msg_msg);
lsm_pr("blob(ns) size %d\n", blob_sizes.lbs_ns);
lsm_pr("blob(sock) size %d\n", blob_sizes.lbs_sock);
lsm_pr("blob(superblock) size %d\n", blob_sizes.lbs_superblock);
lsm_pr("blob(perf_event) size %d\n", blob_sizes.lbs_perf_event);

View File

@ -26,6 +26,7 @@
#include <linux/string.h>
#include <linux/xattr.h>
#include <linux/msg.h>
#include <linux/ns_common.h>
#include <linux/overflow.h>
#include <linux/perf_event.h>
#include <linux/fs.h>
@ -381,6 +382,19 @@ static int lsm_superblock_alloc(struct super_block *sb)
GFP_KERNEL);
}
/**
* lsm_ns_alloc - allocate a composite namespace blob
* @ns: the namespace that needs a blob
*
* Allocate the namespace blob for all the modules
*
* Returns 0, or -ENOMEM if memory can't be allocated.
*/
static int lsm_ns_alloc(struct ns_common *ns)
{
return lsm_blob_alloc(&ns->ns_security, blob_sizes.lbs_ns, GFP_KERNEL);
}
/**
* lsm_fill_user_ctx - Fill a user space lsm_ctx structure
* @uctx: a userspace LSM context to be filled
@ -3357,6 +3371,64 @@ int security_create_user_ns(const struct cred *cred)
return call_int_hook(userns_create, cred);
}
/**
* security_namespace_init() - Initialize LSM security data for a namespace
* @ns: the namespace being initialized
*
* Initialize the LSM security blob attached to the namespace. The namespace type
* is available via ns->ns_type, and the owning user namespace (if any)
* via ns->ops->owner(ns).
*
* Return: Returns 0 if successful, otherwise < 0 error code.
*/
int security_namespace_init(struct ns_common *ns)
{
int rc;
rc = lsm_ns_alloc(ns);
if (unlikely(rc))
return rc;
rc = call_int_hook(namespace_init, ns);
if (unlikely(rc))
security_namespace_free(ns);
return rc;
}
/**
* security_namespace_free() - Release LSM security data from a namespace
* @ns: the namespace being freed
*
* Release security data attached to the namespace. Called before the
* namespace structure is freed.
*/
void security_namespace_free(struct ns_common *ns)
{
if (!ns->ns_security)
return;
call_void_hook(namespace_free, ns);
kfree(ns->ns_security);
ns->ns_security = NULL;
}
/**
* security_namespace_install() - Check permission to install a namespace
* @nsset: the target nsset being configured
* @ns: the namespace being installed
*
* Check permission before allowing a namespace to be installed into the
* process's set of namespaces via setns(2).
*
* Return: Returns 0 if permission is granted, otherwise < 0 error code.
*/
int security_namespace_install(const struct nsset *nsset, struct ns_common *ns)
{
return call_int_hook(namespace_install, nsset, ns);
}
/**
* security_ipc_permission() - Check if sysv ipc access is allowed
* @ipcp: ipc permission structure