milos-linux/kernel/nstree.c

361 lines
9.2 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2025 Christian Brauner <brauner@kernel.org> */
#include <linux/nstree.h>
#include <linux/proc_ns.h>
#include <linux/rculist.h>
#include <linux/vfsdebug.h>
#include <linux/user_namespace.h>
static __cacheline_aligned_in_smp DEFINE_SEQLOCK(ns_tree_lock);
static struct rb_root ns_unified_tree = RB_ROOT; /* protected by ns_tree_lock */
static LIST_HEAD(ns_unified_list); /* protected by ns_tree_lock */
/**
* struct ns_tree - Namespace tree
* @ns_tree: Rbtree of namespaces of a particular type
* @ns_list: Sequentially walkable list of all namespaces of this type
* @type: type of namespaces in this tree
*/
struct ns_tree {
struct rb_root ns_tree;
struct list_head ns_list;
int type;
};
struct ns_tree mnt_ns_tree = {
.ns_tree = RB_ROOT,
.ns_list = LIST_HEAD_INIT(mnt_ns_tree.ns_list),
.type = CLONE_NEWNS,
};
struct ns_tree net_ns_tree = {
.ns_tree = RB_ROOT,
.ns_list = LIST_HEAD_INIT(net_ns_tree.ns_list),
.type = CLONE_NEWNET,
};
EXPORT_SYMBOL_GPL(net_ns_tree);
struct ns_tree uts_ns_tree = {
.ns_tree = RB_ROOT,
.ns_list = LIST_HEAD_INIT(uts_ns_tree.ns_list),
.type = CLONE_NEWUTS,
};
struct ns_tree user_ns_tree = {
.ns_tree = RB_ROOT,
.ns_list = LIST_HEAD_INIT(user_ns_tree.ns_list),
.type = CLONE_NEWUSER,
};
struct ns_tree ipc_ns_tree = {
.ns_tree = RB_ROOT,
.ns_list = LIST_HEAD_INIT(ipc_ns_tree.ns_list),
.type = CLONE_NEWIPC,
};
struct ns_tree pid_ns_tree = {
.ns_tree = RB_ROOT,
.ns_list = LIST_HEAD_INIT(pid_ns_tree.ns_list),
.type = CLONE_NEWPID,
};
struct ns_tree cgroup_ns_tree = {
.ns_tree = RB_ROOT,
.ns_list = LIST_HEAD_INIT(cgroup_ns_tree.ns_list),
.type = CLONE_NEWCGROUP,
};
struct ns_tree time_ns_tree = {
.ns_tree = RB_ROOT,
.ns_list = LIST_HEAD_INIT(time_ns_tree.ns_list),
.type = CLONE_NEWTIME,
};
static inline struct ns_common *node_to_ns(const struct rb_node *node)
{
if (!node)
return NULL;
return rb_entry(node, struct ns_common, ns_tree_node);
}
static inline struct ns_common *node_to_ns_unified(const struct rb_node *node)
{
if (!node)
return NULL;
return rb_entry(node, struct ns_common, ns_unified_tree_node);
}
static inline struct ns_common *node_to_ns_owner(const struct rb_node *node)
{
if (!node)
return NULL;
return rb_entry(node, struct ns_common, ns_owner_tree_node);
}
static int ns_id_cmp(u64 id_a, u64 id_b)
{
if (id_a < id_b)
return -1;
if (id_a > id_b)
return 1;
return 0;
}
static int ns_cmp(struct rb_node *a, const struct rb_node *b)
{
return ns_id_cmp(node_to_ns(a)->ns_id, node_to_ns(b)->ns_id);
}
static int ns_cmp_unified(struct rb_node *a, const struct rb_node *b)
{
return ns_id_cmp(node_to_ns_unified(a)->ns_id, node_to_ns_unified(b)->ns_id);
}
static int ns_cmp_owner(struct rb_node *a, const struct rb_node *b)
{
return ns_id_cmp(node_to_ns_owner(a)->ns_id, node_to_ns_owner(b)->ns_id);
}
void __ns_tree_add_raw(struct ns_common *ns, struct ns_tree *ns_tree)
{
struct rb_node *node, *prev;
const struct proc_ns_operations *ops = ns->ops;
VFS_WARN_ON_ONCE(!ns->ns_id);
VFS_WARN_ON_ONCE(ns->ns_type != ns_tree->type);
write_seqlock(&ns_tree_lock);
node = rb_find_add_rcu(&ns->ns_tree_node, &ns_tree->ns_tree, ns_cmp);
/*
* If there's no previous entry simply add it after the
* head and if there is add it after the previous entry.
*/
prev = rb_prev(&ns->ns_tree_node);
if (!prev)
list_add_rcu(&ns->ns_list_node, &ns_tree->ns_list);
else
list_add_rcu(&ns->ns_list_node, &node_to_ns(prev)->ns_list_node);
/* Add to unified tree and list */
rb_find_add_rcu(&ns->ns_unified_tree_node, &ns_unified_tree, ns_cmp_unified);
prev = rb_prev(&ns->ns_unified_tree_node);
if (!prev)
list_add_rcu(&ns->ns_unified_list_node, &ns_unified_list);
else
list_add_rcu(&ns->ns_unified_list_node, &node_to_ns_unified(prev)->ns_unified_list_node);
if (ops) {
struct user_namespace *user_ns;
VFS_WARN_ON_ONCE(!ops->owner);
user_ns = ops->owner(ns);
if (user_ns) {
struct ns_common *owner = &user_ns->ns;
VFS_WARN_ON_ONCE(owner->ns_type != CLONE_NEWUSER);
/* Insert into owner's rbtree */
rb_find_add_rcu(&ns->ns_owner_tree_node, &owner->ns_owner_tree, ns_cmp_owner);
/* Insert into owner's list in sorted order */
prev = rb_prev(&ns->ns_owner_tree_node);
if (!prev)
list_add_rcu(&ns->ns_owner_entry, &owner->ns_owner);
else
list_add_rcu(&ns->ns_owner_entry, &node_to_ns_owner(prev)->ns_owner_entry);
} else {
/* Only the initial user namespace doesn't have an owner. */
VFS_WARN_ON_ONCE(ns != to_ns_common(&init_user_ns));
}
}
write_sequnlock(&ns_tree_lock);
VFS_WARN_ON_ONCE(node);
ns: add active reference count The namespace tree is, among other things, currently used to support file handles for namespaces. When a namespace is created it is placed on the namespace trees and when it is destroyed it is removed from the namespace trees. While a namespace is on the namespace trees with a valid reference count it is possible to reopen it through a namespace file handle. This is all fine but has some issues that should be addressed. On current kernels a namespace is visible to userspace in the following cases: (1) The namespace is in use by a task. (2) The namespace is persisted through a VFS object (namespace file descriptor or bind-mount). Note that (2) only cares about direct persistence of the namespace itself not indirectly via e.g., file->f_cred file references or similar. (3) The namespace is a hierarchical namespace type and is the parent of a single or multiple child namespaces. Case (3) is interesting because it is possible that a parent namespace might not fulfill any of (1) or (2), i.e., is invisible to userspace but it may still be resurrected through the NS_GET_PARENT ioctl(). Currently namespace file handles allow much broader access to namespaces than what is currently possible via (1)-(3). The reason is that namespaces may remain pinned for completely internal reasons yet are inaccessible to userspace. For example, a user namespace my remain pinned by get_cred() calls to stash the opener's credentials into file->f_cred. As it stands file handles allow to resurrect such a users namespace even though this should not be possible via (1)-(3). This is a fundamental uapi change that we shouldn't do if we don't have to. Consider the following insane case: Various architectures support the CONFIG_MMU_LAZY_TLB_REFCOUNT option which uses lazy TLB destruction. When this option is set a userspace task's struct mm_struct may be used for kernel threads such as the idle task and will only be destroyed once the cpu's runqueue switches back to another task. But because of ptrace() permission checks struct mm_struct stashes the user namespace of the task that struct mm_struct originally belonged to. The kernel thread will take a reference on the struct mm_struct and thus pin it. So on an idle system user namespaces can be persisted for arbitrary amounts of time which also means that they can be resurrected using namespace file handles. That makes no sense whatsoever. The problem is of course excarabted on large systems with a huge number of cpus. To handle this nicely we introduce an active reference count which tracks (1)-(3). This is easy to do as all of these things are already managed centrally. Only (1)-(3) will count towards the active reference count and only namespaces which are active may be opened via namespace file handles. The problem is that namespaces may be resurrected. Which means that they can become temporarily inactive and will be reactived some time later. Currently the only example of this is the SIOGCSKNS socket ioctl. The SIOCGSKNS ioctl allows to open a network namespace file descriptor based on a socket file descriptor. If a socket is tied to a network namespace that subsequently becomes inactive but that socket is persisted by another process in another network namespace (e.g., via SCM_RIGHTS of pidfd_getfd()) then the SIOCGSKNS ioctl will resurrect this network namespace. So calls to open_related_ns() and open_namespace() will end up resurrecting the corresponding namespace tree. Note that the active reference count does not regulate the lifetime of the namespace itself. This is still done by the normal reference count. The active reference count can only be elevated if the regular reference count is elevated. The active reference count also doesn't regulate the presence of a namespace on the namespace trees. It only regulates its visiblity to namespace file handles (and in later patches to listns()). A namespace remains on the namespace trees from creation until its actual destruction. This will allow the kernel to always reach any namespace trivially and it will also enable subsystems like bpf to walk the namespace lists on the system for tracing or general introspection purposes. Note that different namespaces have different visibility lifetimes on current kernels. While most namespace are immediately released when the last task using them exits, the user- and pid namespace are persisted and thus both remain accessible via /proc/<pid>/ns/<ns_type>. The user namespace lifetime is aliged with struct cred and is only released through exit_creds(). However, it becomes inaccessible to userspace once the last task using it is reaped, i.e., when release_task() is called and all proc entries are flushed. Similarly, the pid namespace is also visible until the last task using it has been reaped and the associated pid numbers are freed. The active reference counts of the user- and pid namespace are decremented once the task is reaped. Link: https://patch.msgid.link/20251029-work-namespace-nstree-listns-v4-11-2e6f823ebdc0@kernel.org Signed-off-by: Christian Brauner <brauner@kernel.org>
2025-10-29 13:20:24 +01:00
/*
* Take an active reference on the owner namespace. This ensures
* that the owner remains visible while any of its child namespaces
* are active. For init namespaces this is a no-op as ns_owner()
* returns NULL for namespaces owned by init_user_ns.
*/
__ns_ref_active_get_owner(ns);
}
void __ns_tree_remove(struct ns_common *ns, struct ns_tree *ns_tree)
{
const struct proc_ns_operations *ops = ns->ops;
struct user_namespace *user_ns;
VFS_WARN_ON_ONCE(RB_EMPTY_NODE(&ns->ns_tree_node));
VFS_WARN_ON_ONCE(list_empty(&ns->ns_list_node));
VFS_WARN_ON_ONCE(ns->ns_type != ns_tree->type);
write_seqlock(&ns_tree_lock);
rb_erase(&ns->ns_tree_node, &ns_tree->ns_tree);
RB_CLEAR_NODE(&ns->ns_tree_node);
list_bidir_del_rcu(&ns->ns_list_node);
rb_erase(&ns->ns_unified_tree_node, &ns_unified_tree);
RB_CLEAR_NODE(&ns->ns_unified_tree_node);
list_bidir_del_rcu(&ns->ns_unified_list_node);
/* Remove from owner's rbtree if this namespace has an owner */
if (ops) {
user_ns = ops->owner(ns);
if (user_ns) {
struct ns_common *owner = &user_ns->ns;
rb_erase(&ns->ns_owner_tree_node, &owner->ns_owner_tree);
RB_CLEAR_NODE(&ns->ns_owner_tree_node);
}
list_bidir_del_rcu(&ns->ns_owner_entry);
}
write_sequnlock(&ns_tree_lock);
}
EXPORT_SYMBOL_GPL(__ns_tree_remove);
static int ns_find(const void *key, const struct rb_node *node)
{
const u64 ns_id = *(u64 *)key;
const struct ns_common *ns = node_to_ns(node);
if (ns_id < ns->ns_id)
return -1;
if (ns_id > ns->ns_id)
return 1;
return 0;
}
static int ns_find_unified(const void *key, const struct rb_node *node)
{
const u64 ns_id = *(u64 *)key;
const struct ns_common *ns = node_to_ns_unified(node);
if (ns_id < ns->ns_id)
return -1;
if (ns_id > ns->ns_id)
return 1;
return 0;
}
static struct ns_tree *ns_tree_from_type(int ns_type)
{
switch (ns_type) {
case CLONE_NEWCGROUP:
return &cgroup_ns_tree;
case CLONE_NEWIPC:
return &ipc_ns_tree;
case CLONE_NEWNS:
return &mnt_ns_tree;
case CLONE_NEWNET:
return &net_ns_tree;
case CLONE_NEWPID:
return &pid_ns_tree;
case CLONE_NEWUSER:
return &user_ns_tree;
case CLONE_NEWUTS:
return &uts_ns_tree;
case CLONE_NEWTIME:
return &time_ns_tree;
}
return NULL;
}
static struct ns_common *__ns_unified_tree_lookup_rcu(u64 ns_id)
{
struct rb_node *node;
unsigned int seq;
do {
seq = read_seqbegin(&ns_tree_lock);
node = rb_find_rcu(&ns_id, &ns_unified_tree, ns_find_unified);
if (node)
break;
} while (read_seqretry(&ns_tree_lock, seq));
return node_to_ns_unified(node);
}
static struct ns_common *__ns_tree_lookup_rcu(u64 ns_id, int ns_type)
{
struct ns_tree *ns_tree;
struct rb_node *node;
unsigned int seq;
ns_tree = ns_tree_from_type(ns_type);
if (!ns_tree)
return NULL;
do {
seq = read_seqbegin(&ns_tree_lock);
node = rb_find_rcu(&ns_id, &ns_tree->ns_tree, ns_find);
if (node)
break;
} while (read_seqretry(&ns_tree_lock, seq));
return node_to_ns(node);
}
struct ns_common *ns_tree_lookup_rcu(u64 ns_id, int ns_type)
{
RCU_LOCKDEP_WARN(!rcu_read_lock_held(), "suspicious ns_tree_lookup_rcu() usage");
if (ns_type)
return __ns_tree_lookup_rcu(ns_id, ns_type);
return __ns_unified_tree_lookup_rcu(ns_id);
}
/**
* ns_tree_adjoined_rcu - find the next/previous namespace in the same
* tree
* @ns: namespace to start from
* @previous: if true find the previous namespace, otherwise the next
*
* Find the next or previous namespace in the same tree as @ns. If
* there is no next/previous namespace, -ENOENT is returned.
*/
struct ns_common *__ns_tree_adjoined_rcu(struct ns_common *ns,
struct ns_tree *ns_tree, bool previous)
{
struct list_head *list;
RCU_LOCKDEP_WARN(!rcu_read_lock_held(), "suspicious ns_tree_adjoined_rcu() usage");
if (previous)
list = rcu_dereference(list_bidir_prev_rcu(&ns->ns_list_node));
else
list = rcu_dereference(list_next_rcu(&ns->ns_list_node));
if (list_is_head(list, &ns_tree->ns_list))
return ERR_PTR(-ENOENT);
VFS_WARN_ON_ONCE(list_entry_rcu(list, struct ns_common, ns_list_node)->ns_type != ns_tree->type);
return list_entry_rcu(list, struct ns_common, ns_list_node);
}
/**
* ns_tree_gen_id - generate a new namespace id
* @ns: namespace to generate id for
* @id: if non-zero, this is the initial namespace and this is a fixed id
*
* Generates a new namespace id and assigns it to the namespace. All
* namespaces types share the same id space and thus can be compared
* directly. IOW, when two ids of two namespace are equal, they are
* identical.
*/
u64 __ns_tree_gen_id(struct ns_common *ns, u64 id)
{
static atomic64_t namespace_cookie = ATOMIC64_INIT(NS_LAST_INIT_ID + 1);
if (id)
ns->ns_id = id;
else
ns->ns_id = atomic64_inc_return(&namespace_cookie);
return ns->ns_id;
}