| // SPDX-License-Identifier: GPL-2.0-only | |
| #include <linux/nstree.h> | |
| #include <linux/proc_ns.h> | |
| #include <linux/vfsdebug.h> | |
| /** | |
| * 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 | |
| * @ns_tree_lock: Seqlock to protect the tree and list | |
| * @type: type of namespaces in this tree | |
| */ | |
| struct ns_tree { | |
| struct rb_root ns_tree; | |
| struct list_head ns_list; | |
| seqlock_t ns_tree_lock; | |
| int type; | |
| }; | |
| struct ns_tree mnt_ns_tree = { | |
| .ns_tree = RB_ROOT, | |
| .ns_list = LIST_HEAD_INIT(mnt_ns_tree.ns_list), | |
| .ns_tree_lock = __SEQLOCK_UNLOCKED(mnt_ns_tree.ns_tree_lock), | |
| .type = CLONE_NEWNS, | |
| }; | |
| struct ns_tree net_ns_tree = { | |
| .ns_tree = RB_ROOT, | |
| .ns_list = LIST_HEAD_INIT(net_ns_tree.ns_list), | |
| .ns_tree_lock = __SEQLOCK_UNLOCKED(net_ns_tree.ns_tree_lock), | |
| .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), | |
| .ns_tree_lock = __SEQLOCK_UNLOCKED(uts_ns_tree.ns_tree_lock), | |
| .type = CLONE_NEWUTS, | |
| }; | |
| struct ns_tree user_ns_tree = { | |
| .ns_tree = RB_ROOT, | |
| .ns_list = LIST_HEAD_INIT(user_ns_tree.ns_list), | |
| .ns_tree_lock = __SEQLOCK_UNLOCKED(user_ns_tree.ns_tree_lock), | |
| .type = CLONE_NEWUSER, | |
| }; | |
| struct ns_tree ipc_ns_tree = { | |
| .ns_tree = RB_ROOT, | |
| .ns_list = LIST_HEAD_INIT(ipc_ns_tree.ns_list), | |
| .ns_tree_lock = __SEQLOCK_UNLOCKED(ipc_ns_tree.ns_tree_lock), | |
| .type = CLONE_NEWIPC, | |
| }; | |
| struct ns_tree pid_ns_tree = { | |
| .ns_tree = RB_ROOT, | |
| .ns_list = LIST_HEAD_INIT(pid_ns_tree.ns_list), | |
| .ns_tree_lock = __SEQLOCK_UNLOCKED(pid_ns_tree.ns_tree_lock), | |
| .type = CLONE_NEWPID, | |
| }; | |
| struct ns_tree cgroup_ns_tree = { | |
| .ns_tree = RB_ROOT, | |
| .ns_list = LIST_HEAD_INIT(cgroup_ns_tree.ns_list), | |
| .ns_tree_lock = __SEQLOCK_UNLOCKED(cgroup_ns_tree.ns_tree_lock), | |
| .type = CLONE_NEWCGROUP, | |
| }; | |
| struct ns_tree time_ns_tree = { | |
| .ns_tree = RB_ROOT, | |
| .ns_list = LIST_HEAD_INIT(time_ns_tree.ns_list), | |
| .ns_tree_lock = __SEQLOCK_UNLOCKED(time_ns_tree.ns_tree_lock), | |
| .type = CLONE_NEWTIME, | |
| }; | |
| DEFINE_COOKIE(namespace_cookie); | |
| 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 int ns_cmp(struct rb_node *a, const struct rb_node *b) | |
| { | |
| struct ns_common *ns_a = node_to_ns(a); | |
| struct ns_common *ns_b = node_to_ns(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; | |
| VFS_WARN_ON_ONCE(!ns->ns_id); | |
| write_seqlock(&ns_tree->ns_tree_lock); | |
| VFS_WARN_ON_ONCE(ns->ns_type != ns_tree->type); | |
| 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); | |
| write_sequnlock(&ns_tree->ns_tree_lock); | |
| VFS_WARN_ON_ONCE(node); | |
| } | |
| void __ns_tree_remove(struct ns_common *ns, struct ns_tree *ns_tree) | |
| { | |
| 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->ns_tree_lock); | |
| rb_erase(&ns->ns_tree_node, &ns_tree->ns_tree); | |
| list_bidir_del_rcu(&ns->ns_list_node); | |
| RB_CLEAR_NODE(&ns->ns_tree_node); | |
| write_sequnlock(&ns_tree->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 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; | |
| } | |
| 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; | |
| RCU_LOCKDEP_WARN(!rcu_read_lock_held(), "suspicious ns_tree_lookup_rcu() usage"); | |
| ns_tree = ns_tree_from_type(ns_type); | |
| if (!ns_tree) | |
| return NULL; | |
| do { | |
| seq = read_seqbegin(&ns_tree->ns_tree_lock); | |
| node = rb_find_rcu(&ns_id, &ns_tree->ns_tree, ns_find); | |
| if (node) | |
| break; | |
| } while (read_seqretry(&ns_tree->ns_tree_lock, seq)); | |
| if (!node) | |
| return NULL; | |
| VFS_WARN_ON_ONCE(node_to_ns(node)->ns_type != ns_type); | |
| return node_to_ns(node); | |
| } | |
| /** | |
| * 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 | |
| * | |
| * 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) | |
| { | |
| guard(preempt)(); | |
| ns->ns_id = gen_cookie_next(&namespace_cookie); | |
| return ns->ns_id; | |
| } |