[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20251029-work-namespace-nstree-listns-v4-16-2e6f823ebdc0@kernel.org>
Date: Wed, 29 Oct 2025 13:20:29 +0100
From: Christian Brauner <brauner@...nel.org>
To: linux-fsdevel@...r.kernel.org, Josef Bacik <josef@...icpanda.com>,
Jeff Layton <jlayton@...nel.org>
Cc: Jann Horn <jannh@...gle.com>, Mike Yuan <me@...dnzj.com>,
Zbigniew Jędrzejewski-Szmek <zbyszek@...waw.pl>,
Lennart Poettering <mzxreary@...inter.de>,
Daan De Meyer <daan.j.demeyer@...il.com>, Aleksa Sarai <cyphar@...har.com>,
Amir Goldstein <amir73il@...il.com>, Tejun Heo <tj@...nel.org>,
Johannes Weiner <hannes@...xchg.org>, Thomas Gleixner <tglx@...utronix.de>,
Alexander Viro <viro@...iv.linux.org.uk>, Jan Kara <jack@...e.cz>,
linux-kernel@...r.kernel.org, cgroups@...r.kernel.org, bpf@...r.kernel.org,
Eric Dumazet <edumazet@...gle.com>, Jakub Kicinski <kuba@...nel.org>,
netdev@...r.kernel.org, Arnd Bergmann <arnd@...db.de>,
Christian Brauner <brauner@...nel.org>
Subject: [PATCH v4 16/72] nstree: maintain list of owned namespaces
The namespace tree doesn't express the ownership concept of namespace
appropriately. Maintain a list of directly owned namespaces per user
namespace. This will allow userspace and the kernel to use the listns()
system call to walk the namespace tree by owning user namespace. The
rbtree is used to find the relevant namespace entry point which allows
to continue iteration and the owner list can be used to walk the tree
completely lock free.
Signed-off-by: Christian Brauner <brauner@...nel.org>
---
fs/namespace.c | 2 ++
include/linux/ns_common.h | 6 +++++
init/version-timestamp.c | 2 ++
ipc/msgutil.c | 2 ++
kernel/cgroup/cgroup.c | 2 ++
kernel/nscommon.c | 4 +++
kernel/nstree.c | 68 ++++++++++++++++++++++++++++++++++++++++++++++-
kernel/pid.c | 2 ++
kernel/time/namespace.c | 2 ++
kernel/user.c | 2 ++
10 files changed, 91 insertions(+), 1 deletion(-)
diff --git a/fs/namespace.c b/fs/namespace.c
index 22b4ff6ba134..3e0361c4c138 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -5995,6 +5995,8 @@ struct mnt_namespace init_mnt_ns = {
.mounts = RB_ROOT,
.poll = __WAIT_QUEUE_HEAD_INITIALIZER(init_mnt_ns.poll),
.ns.ns_list_node = LIST_HEAD_INIT(init_mnt_ns.ns.ns_list_node),
+ .ns.ns_owner_entry = LIST_HEAD_INIT(init_mnt_ns.ns.ns_owner_entry),
+ .ns.ns_owner = LIST_HEAD_INIT(init_mnt_ns.ns.ns_owner),
};
static void __init init_mount_tree(void)
diff --git a/include/linux/ns_common.h b/include/linux/ns_common.h
index 88f27b678b4e..e4041603434e 100644
--- a/include/linux/ns_common.h
+++ b/include/linux/ns_common.h
@@ -117,6 +117,12 @@ struct ns_common {
struct rb_node ns_tree_node;
struct list_head ns_list_node;
};
+ struct /* namespace ownership rbtree and list */ {
+ struct rb_root ns_owner_tree; /* rbtree of namespaces owned by this namespace */
+ struct list_head ns_owner; /* list of namespaces owned by this namespace */
+ struct rb_node ns_owner_tree_node; /* node in the owner namespace's rbtree */
+ struct list_head ns_owner_entry; /* node in the owner namespace's ns_owned list */
+ };
atomic_t __ns_ref_active; /* do not use directly */
};
struct rcu_head ns_rcu;
diff --git a/init/version-timestamp.c b/init/version-timestamp.c
index c38498f94646..e5c278dabecf 100644
--- a/init/version-timestamp.c
+++ b/init/version-timestamp.c
@@ -22,6 +22,8 @@ struct uts_namespace init_uts_ns = {
.user_ns = &init_user_ns,
.ns.inum = ns_init_inum(&init_uts_ns),
.ns.ns_list_node = LIST_HEAD_INIT(init_uts_ns.ns.ns_list_node),
+ .ns.ns_owner_entry = LIST_HEAD_INIT(init_uts_ns.ns.ns_owner_entry),
+ .ns.ns_owner = LIST_HEAD_INIT(init_uts_ns.ns.ns_owner),
#ifdef CONFIG_UTS_NS
.ns.ops = &utsns_operations,
#endif
diff --git a/ipc/msgutil.c b/ipc/msgutil.c
index d7c66b430470..ce1de73725c0 100644
--- a/ipc/msgutil.c
+++ b/ipc/msgutil.c
@@ -32,6 +32,8 @@ struct ipc_namespace init_ipc_ns = {
.user_ns = &init_user_ns,
.ns.inum = ns_init_inum(&init_ipc_ns),
.ns.ns_list_node = LIST_HEAD_INIT(init_ipc_ns.ns.ns_list_node),
+ .ns.ns_owner_entry = LIST_HEAD_INIT(init_ipc_ns.ns.ns_owner_entry),
+ .ns.ns_owner = LIST_HEAD_INIT(init_ipc_ns.ns.ns_owner),
#ifdef CONFIG_IPC_NS
.ns.ops = &ipcns_operations,
#endif
diff --git a/kernel/cgroup/cgroup.c b/kernel/cgroup/cgroup.c
index 45e470011c77..9fa082e2eb1a 100644
--- a/kernel/cgroup/cgroup.c
+++ b/kernel/cgroup/cgroup.c
@@ -258,6 +258,8 @@ struct cgroup_namespace init_cgroup_ns = {
.root_cset = &init_css_set,
.ns.ns_type = ns_common_type(&init_cgroup_ns),
.ns.ns_list_node = LIST_HEAD_INIT(init_cgroup_ns.ns.ns_list_node),
+ .ns.ns_owner_entry = LIST_HEAD_INIT(init_cgroup_ns.ns.ns_owner_entry),
+ .ns.ns_owner = LIST_HEAD_INIT(init_cgroup_ns.ns.ns_owner),
};
static struct file_system_type cgroup2_fs_type;
diff --git a/kernel/nscommon.c b/kernel/nscommon.c
index 98a237be64bc..bd4cf8bb8a77 100644
--- a/kernel/nscommon.c
+++ b/kernel/nscommon.c
@@ -62,7 +62,11 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope
ns->ns_type = ns_type;
RB_CLEAR_NODE(&ns->ns_tree_node);
RB_CLEAR_NODE(&ns->ns_unified_tree_node);
+ RB_CLEAR_NODE(&ns->ns_owner_tree_node);
INIT_LIST_HEAD(&ns->ns_list_node);
+ ns->ns_owner_tree = RB_ROOT;
+ INIT_LIST_HEAD(&ns->ns_owner);
+ INIT_LIST_HEAD(&ns->ns_owner_entry);
#ifdef CONFIG_DEBUG_VFS
ns_debug(ns, ops);
diff --git a/kernel/nstree.c b/kernel/nstree.c
index bd6b0a22fd8e..59ec7d6ba302 100644
--- a/kernel/nstree.c
+++ b/kernel/nstree.c
@@ -2,7 +2,9 @@
#include <linux/nstree.h>
#include <linux/proc_ns.h>
+#include <linux/rculist.h>
#include <linux/vfsdebug.h>
+#include <linux/user_namespace.h>
__cacheline_aligned_in_smp DEFINE_SEQLOCK(ns_tree_lock);
static struct rb_root ns_unified_tree = RB_ROOT; /* protected by ns_tree_lock */
@@ -100,6 +102,13 @@ static inline struct ns_common *node_to_ns_unified(const struct rb_node *node)
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 inline int ns_cmp(struct rb_node *a, const struct rb_node *b)
{
struct ns_common *ns_a = node_to_ns(a);
@@ -128,11 +137,27 @@ static inline int ns_cmp_unified(struct rb_node *a, const struct rb_node *b)
return 0;
}
+static inline int ns_cmp_owner(struct rb_node *a, const struct rb_node *b)
+{
+ struct ns_common *ns_a = node_to_ns_owner(a);
+ struct ns_common *ns_b = node_to_ns_owner(b);
+ u64 ns_id_a = ns_a->ns_id;
+ u64 ns_id_b = ns_b->ns_id;
+
+ if (ns_id_a < ns_id_b)
+ return -1;
+ if (ns_id_a > ns_id_b)
+ return 1;
+ return 0;
+}
+
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);
@@ -148,6 +173,30 @@ void __ns_tree_add_raw(struct ns_common *ns, struct ns_tree *ns_tree)
list_add_rcu(&ns->ns_list_node, &node_to_ns(prev)->ns_list_node);
rb_find_add_rcu(&ns->ns_unified_tree_node, &ns_unified_tree, ns_cmp_unified);
+
+ 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);
@@ -163,6 +212,9 @@ void __ns_tree_add_raw(struct ns_common *ns, struct ns_tree *ns_tree)
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);
@@ -170,8 +222,22 @@ void __ns_tree_remove(struct ns_common *ns, struct ns_tree *ns_tree)
write_seqlock(&ns_tree_lock);
rb_erase(&ns->ns_tree_node, &ns_tree->ns_tree);
rb_erase(&ns->ns_unified_tree_node, &ns_unified_tree);
- list_bidir_del_rcu(&ns->ns_list_node);
RB_CLEAR_NODE(&ns->ns_tree_node);
+
+ list_bidir_del_rcu(&ns->ns_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);
diff --git a/kernel/pid.c b/kernel/pid.c
index ec9051d387ee..8134c40b2584 100644
--- a/kernel/pid.c
+++ b/kernel/pid.c
@@ -80,6 +80,8 @@ struct pid_namespace init_pid_ns = {
.user_ns = &init_user_ns,
.ns.inum = ns_init_inum(&init_pid_ns),
.ns.ns_list_node = LIST_HEAD_INIT(init_pid_ns.ns.ns_list_node),
+ .ns.ns_owner_entry = LIST_HEAD_INIT(init_pid_ns.ns.ns_owner_entry),
+ .ns.ns_owner = LIST_HEAD_INIT(init_pid_ns.ns.ns_owner),
#ifdef CONFIG_PID_NS
.ns.ops = &pidns_operations,
#endif
diff --git a/kernel/time/namespace.c b/kernel/time/namespace.c
index 68b67c68670d..f543c4a83229 100644
--- a/kernel/time/namespace.c
+++ b/kernel/time/namespace.c
@@ -484,6 +484,8 @@ struct time_namespace init_time_ns = {
.user_ns = &init_user_ns,
.ns.inum = ns_init_inum(&init_time_ns),
.ns.ops = &timens_operations,
+ .ns.ns_owner_entry = LIST_HEAD_INIT(init_time_ns.ns.ns_owner_entry),
+ .ns.ns_owner = LIST_HEAD_INIT(init_time_ns.ns.ns_owner),
.frozen_offsets = true,
.ns.ns_list_node = LIST_HEAD_INIT(init_time_ns.ns.ns_list_node),
};
diff --git a/kernel/user.c b/kernel/user.c
index bf60532856db..e392768ccd44 100644
--- a/kernel/user.c
+++ b/kernel/user.c
@@ -72,6 +72,8 @@ struct user_namespace init_user_ns = {
.group = GLOBAL_ROOT_GID,
.ns.inum = ns_init_inum(&init_user_ns),
.ns.ns_list_node = LIST_HEAD_INIT(init_user_ns.ns.ns_list_node),
+ .ns.ns_owner_entry = LIST_HEAD_INIT(init_user_ns.ns.ns_owner_entry),
+ .ns.ns_owner = LIST_HEAD_INIT(init_user_ns.ns.ns_owner),
#ifdef CONFIG_USER_NS
.ns.ops = &userns_operations,
#endif
--
2.47.3
Powered by blists - more mailing lists