| // SPDX-License-Identifier: GPL-2.0 | |
| #include "cgroup-internal.h" | |
| #include <linux/sched/task.h> | |
| #include <linux/slab.h> | |
| #include <linux/nsproxy.h> | |
| #include <linux/proc_ns.h> | |
| #include <linux/nstree.h> | |
| /* cgroup namespaces */ | |
| static struct ucounts *inc_cgroup_namespaces(struct user_namespace *ns) | |
| { | |
| return inc_ucount(ns, current_euid(), UCOUNT_CGROUP_NAMESPACES); | |
| } | |
| static void dec_cgroup_namespaces(struct ucounts *ucounts) | |
| { | |
| dec_ucount(ucounts, UCOUNT_CGROUP_NAMESPACES); | |
| } | |
| static struct cgroup_namespace *alloc_cgroup_ns(void) | |
| { | |
| struct cgroup_namespace *new_ns __free(kfree) = NULL; | |
| int ret; | |
| new_ns = kzalloc_obj(struct cgroup_namespace, GFP_KERNEL_ACCOUNT); | |
| if (!new_ns) | |
| return ERR_PTR(-ENOMEM); | |
| ret = ns_common_init(new_ns); | |
| if (ret) | |
| return ERR_PTR(ret); | |
| return no_free_ptr(new_ns); | |
| } | |
| void free_cgroup_ns(struct cgroup_namespace *ns) | |
| { | |
| ns_tree_remove(ns); | |
| put_css_set(ns->root_cset); | |
| dec_cgroup_namespaces(ns->ucounts); | |
| put_user_ns(ns->user_ns); | |
| ns_common_free(ns); | |
| /* Concurrent nstree traversal depends on a grace period. */ | |
| kfree_rcu(ns, ns.ns_rcu); | |
| } | |
| EXPORT_SYMBOL(free_cgroup_ns); | |
| struct cgroup_namespace *copy_cgroup_ns(u64 flags, | |
| struct user_namespace *user_ns, | |
| struct cgroup_namespace *old_ns) | |
| { | |
| struct cgroup_namespace *new_ns; | |
| struct ucounts *ucounts; | |
| struct css_set *cset; | |
| BUG_ON(!old_ns); | |
| if (!(flags & CLONE_NEWCGROUP)) { | |
| get_cgroup_ns(old_ns); | |
| return old_ns; | |
| } | |
| /* Allow only sysadmin to create cgroup namespace. */ | |
| if (!ns_capable(user_ns, CAP_SYS_ADMIN)) | |
| return ERR_PTR(-EPERM); | |
| ucounts = inc_cgroup_namespaces(user_ns); | |
| if (!ucounts) | |
| return ERR_PTR(-ENOSPC); | |
| /* It is not safe to take cgroup_mutex here */ | |
| spin_lock_irq(&css_set_lock); | |
| cset = task_css_set(current); | |
| get_css_set(cset); | |
| spin_unlock_irq(&css_set_lock); | |
| new_ns = alloc_cgroup_ns(); | |
| if (IS_ERR(new_ns)) { | |
| put_css_set(cset); | |
| dec_cgroup_namespaces(ucounts); | |
| return new_ns; | |
| } | |
| new_ns->user_ns = get_user_ns(user_ns); | |
| new_ns->ucounts = ucounts; | |
| new_ns->root_cset = cset; | |
| ns_tree_add(new_ns); | |
| return new_ns; | |
| } | |
| static int cgroupns_install(struct nsset *nsset, struct ns_common *ns) | |
| { | |
| struct nsproxy *nsproxy = nsset->nsproxy; | |
| struct cgroup_namespace *cgroup_ns = to_cg_ns(ns); | |
| if (!ns_capable(nsset->cred->user_ns, CAP_SYS_ADMIN) || | |
| !ns_capable(cgroup_ns->user_ns, CAP_SYS_ADMIN)) | |
| return -EPERM; | |
| /* Don't need to do anything if we are attaching to our own cgroupns. */ | |
| if (cgroup_ns == nsproxy->cgroup_ns) | |
| return 0; | |
| get_cgroup_ns(cgroup_ns); | |
| put_cgroup_ns(nsproxy->cgroup_ns); | |
| nsproxy->cgroup_ns = cgroup_ns; | |
| return 0; | |
| } | |
| static struct ns_common *cgroupns_get(struct task_struct *task) | |
| { | |
| struct cgroup_namespace *ns = NULL; | |
| struct nsproxy *nsproxy; | |
| task_lock(task); | |
| nsproxy = task->nsproxy; | |
| if (nsproxy) { | |
| ns = nsproxy->cgroup_ns; | |
| get_cgroup_ns(ns); | |
| } | |
| task_unlock(task); | |
| return ns ? &ns->ns : NULL; | |
| } | |
| static void cgroupns_put(struct ns_common *ns) | |
| { | |
| put_cgroup_ns(to_cg_ns(ns)); | |
| } | |
| static struct user_namespace *cgroupns_owner(struct ns_common *ns) | |
| { | |
| return to_cg_ns(ns)->user_ns; | |
| } | |
| const struct proc_ns_operations cgroupns_operations = { | |
| .name = "cgroup", | |
| .get = cgroupns_get, | |
| .put = cgroupns_put, | |
| .install = cgroupns_install, | |
| .owner = cgroupns_owner, | |
| }; |