Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > linux.kernel > #1321587 > unrolled thread

CGroup Namespaces (v10)

Started byserge.hallyn@ubuntu.com
First post2016-01-29 10:00 +0100
Last post2016-01-31 18:50 +0100
Articles 6 — 2 participants

Back to article view | Back to linux.kernel


Contents

  CGroup Namespaces (v10) serge.hallyn@ubuntu.com - 2016-01-29 10:00 +0100
    [PATCH 6/8] cgroup: mount cgroupns-root when inside non-init cgroupns serge.hallyn@ubuntu.com - 2016-01-29 10:00 +0100
    [PATCH 7/8] cgroup: Add documentation for cgroup namespaces serge.hallyn@ubuntu.com - 2016-01-29 10:00 +0100
    [PATCH 5/8] kernfs: define kernfs_node_dentry serge.hallyn@ubuntu.com - 2016-01-29 10:00 +0100
    [PATCH 8/8] Add FS_USERNS_FLAG to cgroup fs serge.hallyn@ubuntu.com - 2016-01-29 10:00 +0100
    [PATCH] selftests/cgroupns: new test for cgroup namespaces Alban Crequy <alban.crequy@gmail.com> - 2016-01-31 18:50 +0100

#1321587 — CGroup Namespaces (v10)

Fromserge.hallyn@ubuntu.com
Date2016-01-29 10:00 +0100
SubjectCGroup Namespaces (v10)
Message-ID<qWd4l-cs-5@gated-at.bofh.it>
Hi,

following is a revised set of the CGroup Namespace patchset which Aditya
Kali has previously sent.  The code can also be found in the cgroupns.v10
branch of

https://git.kernel.org/cgit/linux/kernel/git/sergeh/linux-security.git/

To summarize the semantics:

1. CLONE_NEWCGROUP re-uses 0x02000000, which was previously CLONE_STOPPED

2. unsharing a cgroup namespace makes all your current cgroups your new
cgroup root.

3. /proc/pid/cgroup always shows cgroup paths relative to the reader's
cgroup namespce root.  A task outside of  your cgroup looks like

	8:memory:/../../..

4. when a task mounts a cgroupfs, the cgroup which shows up as root depends
on the mounting task's  cgroup namespace.

5. setns to a cgroup namespace switches your cgroup namespace but not
your cgroups.

With this, using github.com/hallyn/lxc #2015-11-09/cgns (and
github.com/hallyn/lxcfs #2015-11-10/cgns) we can start a container in a full
proper cgroup namespace, avoiding either cgmanager or lxcfs cgroup bind mounts.

This is completely backward compatible and will be completely invisible
to any existing cgroup users (except for those running inside a cgroup
namespace and looking at /proc/pid/cgroup of tasks outside their
namespace.)

Changes from V9:
1. Update to latest Linus tree
2. A few locking fixes

Changes from V8:
1. Incorporate updated documentation from tj.
2. Put lookup_one_len() under inode lock
3. Make cgroup_path non-namespaced, so only calls to cgroup_path_ns() are
   namespaced.
4. Make cgroup_path{,_ns} take the needed locks, since external callers cannot
   do so.
5. Fix the bisectability problem of to_cg_ns() being defined after use

Changes from V7:
1. Rework kernfs_path_from_node_locked to return the string length
2. Rename and reorder args to kernfs_path_from_node
3. cgroup.c: undo accidental conversoins to inline
4. cgroup.h: move ns declarations to bottom.
5. Rework the documentation to fit the style of the rest of cgroup.txt

Changes from V6:
1. Switch to some WARN_ONs to provide stack traces
2. Rename kernfs_node_distance to kernfs_depth
3. Make sure kernfs_common_ancestor() nodes are from same root
4. Split kernfs changes for cgroup_mount into separate patch
5. Rename kernfs_obtain_root to kernfs_node_dentry
(And more, see patch changelogs)

Changes from V5:
1. To get a root dentry for cgroup namespace mount, walk the path from the
   kernfs root dentry.

Changes from V4:
1. Move the FS_USERNS_MOUNT flag to last patch
2. Rebase onto cgroup/for-4.5
3. Don't non-init user namespaces to bind new subsystems when mounting.
4. Address feedback from Tejun (thanks).  Specificaly, not addressed:
   . kernfs_obtain_root - walking dentry from kernfs root.
     (I think that's the only piece)
5. Dropped unused get_task_cgroup fn/patch.
6. Reworked kernfs_path_from_node_locked() to try to simplify the logic.
   It now finds a common ancestor, walks from the source to it, then back
   up to the target.

Changes from V3:
1. Rebased onto latest cgroup changes.  In particular switch to
   css_set_lock and ns_common.
2. Support all hierarchies.

Changes from V2:
1. Added documentation in Documentation/cgroups/namespace.txt
2. Fixed a bug that caused crash
3. Incorporated some other suggestions from last patchset:
   - removed use of threadgroup_lock() while creating new cgroupns
   - use task_lock() instead of rcu_read_lock() while accessing
     task->nsproxy
   - optimized setns() to own cgroupns
   - simplified code around sane-behavior mount option parsing
4. Restored ACKs from Serge Hallyn from v1 on few patches that have
   not changed since then.

Changes from V1:
1. No pinning of processes within cgroupns. Tasks can be freely moved
   across cgroups even outside of their cgroupns-root. Usual DAC/MAC policies
   apply as before.
2. Path in /proc/<pid>/cgroup is now always shown and is relative to
   cgroupns-root. So path can contain '/..' strings depending on cgroupns-root
   of the reader and cgroup of <pid>.
3. setns() does not require the process to first move under target
   cgroupns-root.

Changes form RFC (V0):
1. setns support for cgroupns
2. 'mount -t cgroup cgroup <mntpt>' from inside a cgroupns now
   mounts the cgroup hierarcy with cgroupns-root as the filesystem root.
3. writes to cgroup files outside of cgroupns-root are not allowed
4. visibility of /proc/<pid>/cgroup is further restricted by not showing
   anything if the <pid> is in a sibling cgroupns and its cgroup falls outside
   your cgroupns-root.

[toc] | [next] | [standalone]


#1321588 — [PATCH 6/8] cgroup: mount cgroupns-root when inside non-init cgroupns

Fromserge.hallyn@ubuntu.com
Date2016-01-29 10:00 +0100
Subject[PATCH 6/8] cgroup: mount cgroupns-root when inside non-init cgroupns
Message-ID<qWd4m-cs-23@gated-at.bofh.it>
In reply to#1321587
From: Serge Hallyn <serge.hallyn@ubuntu.com>

This patch enables cgroup mounting inside userns when a process
as appropriate privileges. The cgroup filesystem mounted is
rooted at the cgroupns-root. Thus, in a container-setup, only
the hierarchy under the cgroupns-root is exposed inside the container.
This allows container management tools to run inside the containers
without depending on any global state.

Signed-off-by: Serge Hallyn <serge.hallyn@canonical.com>
---
Changelog:
	20151116 - Don't allow user namespaces to bind new subsystems
	20151118 - postpone the FS_USERNS_MOUNT flag until the
	           last patch, until we can convince ourselves it
		   is safe.
	20151207 - Switch to walking up the kernfs path from kn root.
		 - Group initialized variables
		 - Explain the capable(CAP_SYS_ADMIN) check
		 - Style fixes
	20160104 - kernfs_node_dentry: lock inode for lookup_one_len()
	20160128 - grab needed lock in mount

Signed-off-by: Serge Hallyn <serge.hallyn@ubuntu.com>
---
 kernel/cgroup.c |   48 +++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 47 insertions(+), 1 deletion(-)

diff --git a/kernel/cgroup.c b/kernel/cgroup.c
index 96e3dab..3e04df0 100644
--- a/kernel/cgroup.c
+++ b/kernel/cgroup.c
@@ -1983,6 +1983,7 @@ static struct dentry *cgroup_mount(struct file_system_type *fs_type,
 {
 	bool is_v2 = fs_type == &cgroup2_fs_type;
 	struct super_block *pinned_sb = NULL;
+	struct cgroup_namespace *ns = current->nsproxy->cgroup_ns;
 	struct cgroup_subsys *ss;
 	struct cgroup_root *root;
 	struct cgroup_sb_opts opts;
@@ -1991,6 +1992,14 @@ static struct dentry *cgroup_mount(struct file_system_type *fs_type,
 	int i;
 	bool new_sb;
 
+	get_cgroup_ns(ns);
+
+	/* Check if the caller has permission to mount. */
+	if (!ns_capable(ns->user_ns, CAP_SYS_ADMIN)) {
+		put_cgroup_ns(ns);
+		return ERR_PTR(-EPERM);
+	}
+
 	/*
 	 * The first time anyone tries to mount a cgroup, enable the list
 	 * linking each css_set to its tasks and fix up all existing tasks.
@@ -2001,6 +2010,7 @@ static struct dentry *cgroup_mount(struct file_system_type *fs_type,
 	if (is_v2) {
 		if (data) {
 			pr_err("cgroup2: unknown option \"%s\"\n", (char *)data);
+			put_cgroup_ns(ns);
 			return ERR_PTR(-EINVAL);
 		}
 		cgrp_dfl_root_visible = true;
@@ -2106,6 +2116,16 @@ static struct dentry *cgroup_mount(struct file_system_type *fs_type,
 		goto out_unlock;
 	}
 
+	/*
+	 * We know this subsystem has not yet been bound.  Users in a non-init
+	 * user namespace may only mount hierarchies with no bound subsystems,
+	 * i.e. 'none,name=user1'
+	 */
+	if (!opts.none && !capable(CAP_SYS_ADMIN)) {
+		ret = -EPERM;
+		goto out_unlock;
+	}
+
 	root = kzalloc(sizeof(*root), GFP_KERNEL);
 	if (!root) {
 		ret = -ENOMEM;
@@ -2124,12 +2144,37 @@ out_free:
 	kfree(opts.release_agent);
 	kfree(opts.name);
 
-	if (ret)
+	if (ret) {
+		put_cgroup_ns(ns);
 		return ERR_PTR(ret);
+	}
 out_mount:
 	dentry = kernfs_mount(fs_type, flags, root->kf_root,
 			      is_v2 ? CGROUP2_SUPER_MAGIC : CGROUP_SUPER_MAGIC,
 			      &new_sb);
+
+	/*
+	 * In non-init cgroup namespace, instead of root cgroup's
+	 * dentry, we return the dentry corresponding to the
+	 * cgroupns->root_cgrp.
+	 */
+	if (!IS_ERR(dentry) && ns != &init_cgroup_ns) {
+		struct dentry *nsdentry;
+		struct cgroup *cgrp;
+
+		mutex_lock(&cgroup_mutex);
+		spin_lock_bh(&css_set_lock);
+
+		cgrp = cset_cgroup_from_root(ns->root_cset, root);
+
+		spin_unlock_bh(&css_set_lock);
+		mutex_unlock(&cgroup_mutex);
+
+		nsdentry = kernfs_node_dentry(cgrp->kn, dentry->d_sb);
+		dput(dentry);
+		dentry = nsdentry;
+	}
+
 	if (IS_ERR(dentry) || !new_sb)
 		cgroup_put(&root->cgrp);
 
@@ -2142,6 +2187,7 @@ out_mount:
 		deactivate_super(pinned_sb);
 	}
 
+	put_cgroup_ns(ns);
 	return dentry;
 }
 
-- 
1.7.9.5

[toc] | [prev] | [next] | [standalone]


#1321590 — [PATCH 7/8] cgroup: Add documentation for cgroup namespaces

Fromserge.hallyn@ubuntu.com
Date2016-01-29 10:00 +0100
Subject[PATCH 7/8] cgroup: Add documentation for cgroup namespaces
Message-ID<qWd4m-cs-25@gated-at.bofh.it>
In reply to#1321587
From: Serge Hallyn <serge.hallyn@ubuntu.com>

Signed-off-by: Aditya Kali <adityakali@google.com>
Signed-off-by: Serge Hallyn <serge.hallyn@canonical.com>
Signed-off-by: Tejun Heo <tj@kernel.org>

---
Changelog (2015-12-08):
  Merge into Documentation/cgroup.txt
Changelog (2015-12-22):
  Reformat to try to follow the style of the rest of the cgroup.txt file.
Changelog (2015-12-22):
  tj: Reorganized to better fit the documentation.
---
 Documentation/cgroup-v2.txt |  147 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 147 insertions(+)

diff --git a/Documentation/cgroup-v2.txt b/Documentation/cgroup-v2.txt
index 65b3eac..eee9012 100644
--- a/Documentation/cgroup-v2.txt
+++ b/Documentation/cgroup-v2.txt
@@ -47,6 +47,11 @@ CONTENTS
   5-3. IO
     5-3-1. IO Interface Files
     5-3-2. Writeback
+6. Namespace
+  6-1. Basics
+  6-2. The Root and Views
+  6-3. Migration and setns(2)
+  6-4. Interaction with Other Namespaces
 P. Information on Kernel Programming
   P-1. Filesystem Support for Writeback
 D. Deprecated v1 Core Features
@@ -1085,6 +1090,148 @@ writeback as follows.
 	vm.dirty[_background]_ratio.
 
 
+6. Namespace
+
+6-1. Basics
+
+cgroup namespace provides a mechanism to virtualize the view of the
+"/proc/$PID/cgroup" file and cgroup mounts.  The CLONE_NEWCGROUP clone
+flag can be used with clone(2) and unshare(2) to create a new cgroup
+namespace.  The process running inside the cgroup namespace will have
+its "/proc/$PID/cgroup" output restricted to cgroupns root.  The
+cgroupns root is the cgroup of the process at the time of creation of
+the cgroup namespace.
+
+Without cgroup namespace, the "/proc/$PID/cgroup" file shows the
+complete path of the cgroup of a process.  In a container setup where
+a set of cgroups and namespaces are intended to isolate processes the
+"/proc/$PID/cgroup" file may leak potential system level information
+to the isolated processes.  For Example:
+
+  # cat /proc/self/cgroup
+  0::/batchjobs/container_id1
+
+The path '/batchjobs/container_id1' can be considered as system-data
+and undesirable to expose to the isolated processes.  cgroup namespace
+can be used to restrict visibility of this path.  For example, before
+creating a cgroup namespace, one would see:
+
+  # ls -l /proc/self/ns/cgroup
+  lrwxrwxrwx 1 root root 0 2014-07-15 10:37 /proc/self/ns/cgroup -> cgroup:[4026531835]
+  # cat /proc/self/cgroup
+  0::/batchjobs/container_id1
+
+After unsharing a new namespace, the view changes.
+
+  # ls -l /proc/self/ns/cgroup
+  lrwxrwxrwx 1 root root 0 2014-07-15 10:35 /proc/self/ns/cgroup -> cgroup:[4026532183]
+  # cat /proc/self/cgroup
+  0::/
+
+When some thread from a multi-threaded process unshares its cgroup
+namespace, the new cgroupns gets applied to the entire process (all
+the threads).  This is natural for the v2 hierarchy; however, for the
+legacy hierarchies, this may be unexpected.
+
+A cgroup namespace is alive as long as there are processes inside or
+mounts pinning it.  When the last usage goes away, the cgroup
+namespace is destroyed.  The cgroupns root and the actual cgroups
+remain.
+
+
+6-2. The Root and Views
+
+The 'cgroupns root' for a cgroup namespace is the cgroup in which the
+process calling unshare(2) is running.  For example, if a process in
+/batchjobs/container_id1 cgroup calls unshare, cgroup
+/batchjobs/container_id1 becomes the cgroupns root.  For the
+init_cgroup_ns, this is the real root ('/') cgroup.
+
+The cgroupns root cgroup does not change even if the namespace creator
+process later moves to a different cgroup.
+
+  # ~/unshare -c # unshare cgroupns in some cgroup
+  # cat /proc/self/cgroup
+  0::/
+  # mkdir sub_cgrp_1
+  # echo 0 > sub_cgrp_1/cgroup.procs
+  # cat /proc/self/cgroup
+  0::/sub_cgrp_1
+
+Each process gets its namespace-specific view of "/proc/$PID/cgroup"
+
+Processes running inside the cgroup namespace will be able to see
+cgroup paths (in /proc/self/cgroup) only inside their root cgroup.
+From within an unshared cgroupns:
+
+  # sleep 100000 &
+  [1] 7353
+  # echo 7353 > sub_cgrp_1/cgroup.procs
+  # cat /proc/7353/cgroup
+  0::/sub_cgrp_1
+
+From the initial cgroup namespace, the real cgroup path will be
+visible:
+
+  $ cat /proc/7353/cgroup
+  0::/batchjobs/container_id1/sub_cgrp_1
+
+From a sibling cgroup namespace (that is, a namespace rooted at a
+different cgroup), the cgroup path relative to its own cgroup
+namespace root will be shown.  For instance, if PID 7353's cgroup
+namespace root is at '/batchjobs/container_id2', then it will see
+
+  # cat /proc/7353/cgroup
+  0::/../container_id2/sub_cgrp_1
+
+Note that the relative path always starts with '/' to indicate that
+its relative to the cgroup namespace root of the caller.
+
+
+6-3. Migration and setns(2)
+
+Processes inside a cgroup namespace can move into and out of the
+namespace root if they have proper access to external cgroups.  For
+example, from inside a namespace with cgroupns root at
+/batchjobs/container_id1, and assuming that the global hierarchy is
+still accessible inside cgroupns:
+
+  # cat /proc/7353/cgroup
+  0::/sub_cgrp_1
+  # echo 7353 > batchjobs/container_id2/cgroup.procs
+  # cat /proc/7353/cgroup
+  0::/../container_id2
+
+Note that this kind of setup is not encouraged.  A task inside cgroup
+namespace should only be exposed to its own cgroupns hierarchy.
+
+setns(2) to another cgroup namespace is allowed when:
+
+(a) the process has CAP_SYS_ADMIN against its current user namespace
+(b) the process has CAP_SYS_ADMIN against the target cgroup
+    namespace's userns
+
+No implicit cgroup changes happen with attaching to another cgroup
+namespace.  It is expected that the someone moves the attaching
+process under the target cgroup namespace root.
+
+
+6-4. Interaction with Other Namespaces
+
+Namespace specific cgroup hierarchy can be mounted by a process
+running inside a non-init cgroup namespace.
+
+  # mount -t cgroup2 none $MOUNT_POINT
+
+This will mount the unified cgroup hierarchy with cgroupns root as the
+filesystem root.  The process needs CAP_SYS_ADMIN against its user and
+mount namespaces.
+
+The virtualization of /proc/self/cgroup file combined with restricting
+the view of cgroup hierarchy by namespace-private cgroupfs mount
+provides a properly isolated cgroup view inside the container.
+
+
 P. Information on Kernel Programming
 
 This section contains kernel programming information in the areas
-- 
1.7.9.5

[toc] | [prev] | [next] | [standalone]


#1321592 — [PATCH 5/8] kernfs: define kernfs_node_dentry

Fromserge.hallyn@ubuntu.com
Date2016-01-29 10:00 +0100
Subject[PATCH 5/8] kernfs: define kernfs_node_dentry
Message-ID<qWd4m-cs-29@gated-at.bofh.it>
In reply to#1321587
From: Aditya Kali <adityakali@google.com>

Add a new kernfs api is added to lookup the dentry for a particular
kernfs path.

Signed-off-by: Aditya Kali <adityakali@google.com>
Signed-off-by: Serge E. Hallyn <serge.hallyn@canonical.com>
Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
Changelog:
	20151116 - Don't allow user namespaces to bind new subsystems
	20151118 - postpone the FS_USERNS_MOUNT flag until the
	           last patch, until we can convince ourselves it
		   is safe.
	20151207 - Switch to walking up the kernfs path from kn root.
	20151208 - Split out the kernfs change
		 - Style changes
		 - Switch from pr_crit to WARN_ON
		 - Reorder arguments to kernfs_obtain_root
		 - rename kernfs_obtain_root to kernfs_node_dentry
	20160104 - kernfs_node_dentry: lock inode for lookup_one_len()
---
 fs/kernfs/mount.c      |   69 ++++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/kernfs.h |    2 ++
 2 files changed, 71 insertions(+)

diff --git a/fs/kernfs/mount.c b/fs/kernfs/mount.c
index 8eaf417..074bb8b 100644
--- a/fs/kernfs/mount.c
+++ b/fs/kernfs/mount.c
@@ -14,6 +14,7 @@
 #include <linux/magic.h>
 #include <linux/slab.h>
 #include <linux/pagemap.h>
+#include <linux/namei.h>
 
 #include "kernfs-internal.h"
 
@@ -62,6 +63,74 @@ struct kernfs_root *kernfs_root_from_sb(struct super_block *sb)
 	return NULL;
 }
 
+/*
+ * find the next ancestor in the path down to @child, where @parent was the
+ * ancestor whose descendant we want to find.
+ *
+ * Say the path is /a/b/c/d.  @child is d, @parent is NULL.  We return the root
+ * node.  If @parent is b, then we return the node for c.
+ * Passing in d as @parent is not ok.
+ */
+static struct kernfs_node *
+find_next_ancestor(struct kernfs_node *child, struct kernfs_node *parent)
+{
+	if (child == parent) {
+		pr_crit_once("BUG in find_next_ancestor: called with parent == child");
+		return NULL;
+	}
+
+	while (child->parent != parent) {
+		if (!child->parent)
+			return NULL;
+		child = child->parent;
+	}
+
+	return child;
+}
+
+/**
+ * kernfs_node_dentry - get a dentry for the given kernfs_node
+ * @kn: kernfs_node for which a dentry is needed
+ * @sb: the kernfs super_block
+ */
+struct dentry *kernfs_node_dentry(struct kernfs_node *kn,
+				  struct super_block *sb)
+{
+	struct dentry *dentry;
+	struct kernfs_node *knparent = NULL;
+
+	BUG_ON(sb->s_op != &kernfs_sops);
+
+	dentry = dget(sb->s_root);
+
+	/* Check if this is the root kernfs_node */
+	if (!kn->parent)
+		return dentry;
+
+	knparent = find_next_ancestor(kn, NULL);
+	if (WARN_ON(!knparent))
+		return ERR_PTR(-EINVAL);
+
+	do {
+		struct dentry *dtmp;
+		struct kernfs_node *kntmp;
+
+		if (kn == knparent)
+			return dentry;
+		kntmp = find_next_ancestor(kn, knparent);
+		if (WARN_ON(!kntmp))
+			return ERR_PTR(-EINVAL);
+		mutex_lock(&d_inode(dentry)->i_mutex);
+		dtmp = lookup_one_len(kntmp->name, dentry, strlen(kntmp->name));
+		mutex_unlock(&d_inode(dentry)->i_mutex);
+		dput(dentry);
+		if (IS_ERR(dtmp))
+			return dtmp;
+		knparent = kntmp;
+		dentry = dtmp;
+	} while (1);
+}
+
 static int kernfs_fill_super(struct super_block *sb, unsigned long magic)
 {
 	struct kernfs_super_info *info = kernfs_info(sb);
diff --git a/include/linux/kernfs.h b/include/linux/kernfs.h
index 716bfde..c06c442 100644
--- a/include/linux/kernfs.h
+++ b/include/linux/kernfs.h
@@ -284,6 +284,8 @@ struct kernfs_node *kernfs_node_from_dentry(struct dentry *dentry);
 struct kernfs_root *kernfs_root_from_sb(struct super_block *sb);
 struct inode *kernfs_get_inode(struct super_block *sb, struct kernfs_node *kn);
 
+struct dentry *kernfs_node_dentry(struct kernfs_node *kn,
+				  struct super_block *sb);
 struct kernfs_root *kernfs_create_root(struct kernfs_syscall_ops *scops,
 				       unsigned int flags, void *priv);
 void kernfs_destroy_root(struct kernfs_root *root);
-- 
1.7.9.5

[toc] | [prev] | [next] | [standalone]


#1321593 — [PATCH 8/8] Add FS_USERNS_FLAG to cgroup fs

Fromserge.hallyn@ubuntu.com
Date2016-01-29 10:00 +0100
Subject[PATCH 8/8] Add FS_USERNS_FLAG to cgroup fs
Message-ID<qWd4m-cs-21@gated-at.bofh.it>
In reply to#1321587
From: Serge Hallyn <serge.hallyn@ubuntu.com>

allowing root in a non-init user namespace to mount it.  This should
now be safe, because

1. non-init-root cannot mount a previously unbound subsystem
2. the task doing the mount must be privileged with respect to the
   user namespace owning the cgroup namespace
3. the mounted subsystem will have its current cgroup as the root dentry.
   the permissions will be unchanged, so tasks will receive no new
   privilege over the cgroups which they did not have on the original
   mounts.

Signed-off-by: Serge Hallyn <serge.hallyn@canonical.com>
---
 kernel/cgroup.c |    2 ++
 1 file changed, 2 insertions(+)

diff --git a/kernel/cgroup.c b/kernel/cgroup.c
index 3e04df0..7a58749 100644
--- a/kernel/cgroup.c
+++ b/kernel/cgroup.c
@@ -2216,12 +2216,14 @@ static struct file_system_type cgroup_fs_type = {
 	.name = "cgroup",
 	.mount = cgroup_mount,
 	.kill_sb = cgroup_kill_sb,
+	.fs_flags = FS_USERNS_MOUNT,
 };
 
 static struct file_system_type cgroup2_fs_type = {
 	.name = "cgroup2",
 	.mount = cgroup_mount,
 	.kill_sb = cgroup_kill_sb,
+	.fs_flags = FS_USERNS_MOUNT,
 };
 
 static char *
-- 
1.7.9.5

[toc] | [prev] | [next] | [standalone]


#1322693 — [PATCH] selftests/cgroupns: new test for cgroup namespaces

FromAlban Crequy <alban.crequy@gmail.com>
Date2016-01-31 18:50 +0100
Subject[PATCH] selftests/cgroupns: new test for cgroup namespaces
Message-ID<qX4im-6H4-11@gated-at.bofh.it>
In reply to#1321587
From: Alban Crequy <alban@kinvolk.io>

This adds the selftest "cgroupns_test" in order to test the CGroup
Namespace patchset.

cgroupns_test creates two child processes. They perform a list of
actions defined by the array cgroupns_test. This array can easily be
extended to more scenarios without adding much code. They are
synchronized with eventfds to ensure only one action is performed at a
time.

The memory is shared between the processes (CLONE_VM) so each child
process can know the pid of their siblings without extra IPC.

The output explains the scenario being played. Short extract:

> current cgroup: /user.slice/user-0.slice/session-1.scope
> child process #0: check that process #self (pid=482) has cgroup /user.slice/user-0.slice/session-1.scope
> child process #0: unshare cgroupns
> child process #0: move process #self (pid=482) to cgroup cgroup-a/subcgroup-a
> child process #0: join parent cgroupns

The test does not change the mount namespace and does not mount any
new cgroup2 filesystem. Therefore this does not test that the cgroup2
mount is correctly rooted to the cgroupns root at mount time.

Signed-off-by: Alban Crequy <alban@kinvolk.io>
Acked-by: Serge E. Hallyn <serge.hallyn@ubuntu.com>

---

Changelog:
    20160131 - rebase on sergeh/cgroupns.v10 and fix conflicts

    20160115 - Detect where cgroup2 is mounted, don't assume
               /sys/fs/cgroup (suggested by sergeh)
             - Check more error conditions (from krnowak's review)
             - Coding style (from krnowak's review)
             - Update error message for Linux >= 4.5 (from krnowak's
               review)

    20160104 - Fix coding style (from sergeh's review)
             - Fix printf formatting (from sergeh's review)
             - Fix parsing of /proc/pid/cgroup (from sergeh's review)
             - Fix concatenation of cgroup paths

    20151219 - First version

This patch is available in the cgroupns.v10-tests branch of
https://github.com/kinvolk/linux.git
It is rebased on top of Serge Hallyn's cgroupns.v10 branch of
https://git.kernel.org/cgit/linux/kernel/git/sergeh/linux-security.git/

Test results:

- SUCCESS on kernel cgroupns.v10 booted with systemd.unified_cgroup_hierarchy=1
- SUCCESS on kernel cgroupns.v10 booted with systemd.unified_cgroup_hierarchy=0
---
 tools/testing/selftests/Makefile                 |   1 +
 tools/testing/selftests/cgroupns/Makefile        |  11 +
 tools/testing/selftests/cgroupns/cgroupns_test.c | 445 +++++++++++++++++++++++
 3 files changed, 457 insertions(+)
 create mode 100644 tools/testing/selftests/cgroupns/Makefile
 create mode 100644 tools/testing/selftests/cgroupns/cgroupns_test.c

diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index b04afc3..b373135 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -1,5 +1,6 @@
 TARGETS = breakpoints
 TARGETS += capabilities
+TARGETS += cgroupns
 TARGETS += cpu-hotplug
 TARGETS += efivarfs
 TARGETS += exec
diff --git a/tools/testing/selftests/cgroupns/Makefile b/tools/testing/selftests/cgroupns/Makefile
new file mode 100644
index 0000000..0fdbe0a
--- /dev/null
+++ b/tools/testing/selftests/cgroupns/Makefile
@@ -0,0 +1,11 @@
+CFLAGS += -I../../../../usr/include/
+CFLAGS += -I../../../../include/uapi/
+
+all: cgroupns_test
+
+TEST_PROGS := cgroupns_test
+
+include ../lib.mk
+
+clean:
+	$(RM) cgroupns_test
diff --git a/tools/testing/selftests/cgroupns/cgroupns_test.c b/tools/testing/selftests/cgroupns/cgroupns_test.c
new file mode 100644
index 0000000..71e2336
--- /dev/null
+++ b/tools/testing/selftests/cgroupns/cgroupns_test.c
@@ -0,0 +1,445 @@
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/statfs.h>
+#include <inttypes.h>
+#include <sched.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <sys/eventfd.h>
+#include <signal.h>
+#include <fcntl.h>
+
+#include <linux/magic.h>
+#include <linux/sched.h>
+
+#include "../kselftest.h"
+
+#define STACK_SIZE 65536
+
+static char cgroup_mountpoint[4096];
+static char root_cgroup[4096];
+
+#define CHILDREN_COUNT 2
+typedef struct {
+	pid_t pid;
+	uint8_t *stack;
+	int start_semfd;
+	int end_semfd;
+} cgroupns_child_t;
+cgroupns_child_t children[CHILDREN_COUNT];
+
+typedef enum {
+	UNSHARE_CGROUPNS,
+	JOIN_CGROUPNS,
+	CHECK_CGROUP,
+	CHECK_CGROUP_WITH_ROOT_PREFIX,
+	MOVE_CGROUP,
+	MOVE_CGROUP_WITH_ROOT_PREFIX,
+} cgroupns_action_t;
+
+static const struct {
+	int actor_id;
+	cgroupns_action_t action;
+	int target_id;
+	char *path;
+} cgroupns_tests[] = {
+	{ 0, CHECK_CGROUP_WITH_ROOT_PREFIX, -1, "/"},
+	{ 0, CHECK_CGROUP_WITH_ROOT_PREFIX, 0, "/"},
+	{ 0, CHECK_CGROUP_WITH_ROOT_PREFIX, 1, "/"},
+	{ 1, CHECK_CGROUP_WITH_ROOT_PREFIX, -1, "/"},
+	{ 1, CHECK_CGROUP_WITH_ROOT_PREFIX, 0, "/"},
+	{ 1, CHECK_CGROUP_WITH_ROOT_PREFIX, 1, "/"},
+
+	{ 0, UNSHARE_CGROUPNS, -1, NULL},
+
+	{ 0, CHECK_CGROUP, -1, "/"},
+	{ 0, CHECK_CGROUP, 0, "/"},
+	{ 0, CHECK_CGROUP, 1, "/"},
+	{ 1, CHECK_CGROUP_WITH_ROOT_PREFIX, -1, "/"},
+	{ 1, CHECK_CGROUP_WITH_ROOT_PREFIX, 0, "/"},
+	{ 1, CHECK_CGROUP_WITH_ROOT_PREFIX, 1, "/"},
+
+	{ 1, UNSHARE_CGROUPNS, -1, NULL},
+
+	{ 0, CHECK_CGROUP, -1, "/"},
+	{ 0, CHECK_CGROUP, 0, "/"},
+	{ 0, CHECK_CGROUP, 1, "/"},
+	{ 1, CHECK_CGROUP, -1, "/"},
+	{ 1, CHECK_CGROUP, 0, "/"},
+	{ 1, CHECK_CGROUP, 1, "/"},
+
+	{ 0, MOVE_CGROUP_WITH_ROOT_PREFIX, -1, "cgroup-a"},
+	{ 1, MOVE_CGROUP_WITH_ROOT_PREFIX, -1, "cgroup-b"},
+
+	{ 0, CHECK_CGROUP, -1, "/cgroup-a"},
+	{ 0, CHECK_CGROUP, 0, "/cgroup-a"},
+	{ 0, CHECK_CGROUP, 1, "/cgroup-b"},
+	{ 1, CHECK_CGROUP, -1, "/cgroup-b"},
+	{ 1, CHECK_CGROUP, 0, "/cgroup-a"},
+	{ 1, CHECK_CGROUP, 1, "/cgroup-b"},
+
+	{ 0, UNSHARE_CGROUPNS, -1, NULL},
+	{ 1, UNSHARE_CGROUPNS, -1, NULL},
+
+	{ 0, CHECK_CGROUP, -1, "/"},
+	{ 0, CHECK_CGROUP, 0, "/"},
+	{ 0, CHECK_CGROUP, 1, "/../cgroup-b"},
+	{ 1, CHECK_CGROUP, -1, "/"},
+	{ 1, CHECK_CGROUP, 0, "/../cgroup-a"},
+	{ 1, CHECK_CGROUP, 1, "/"},
+
+	{ 0, MOVE_CGROUP_WITH_ROOT_PREFIX, -1, "cgroup-a/sub1-a"},
+	{ 1, MOVE_CGROUP_WITH_ROOT_PREFIX, -1, "cgroup-b/sub1-b"},
+
+	{ 0, CHECK_CGROUP, 0, "/sub1-a"},
+	{ 0, CHECK_CGROUP, 1, "/../cgroup-b/sub1-b"},
+	{ 1, CHECK_CGROUP, 0, "/../cgroup-a/sub1-a"},
+	{ 1, CHECK_CGROUP, 1, "/sub1-b"},
+
+	{ 0, MOVE_CGROUP_WITH_ROOT_PREFIX, -1, "cgroup-a/sub1-a/sub2-a"},
+	{ 1, CHECK_CGROUP, 0, "/../cgroup-a/sub1-a/sub2-a"},
+	{ 0, CHECK_CGROUP, 1, "/../cgroup-b/sub1-b"},
+	{ 0, MOVE_CGROUP_WITH_ROOT_PREFIX, -1, "cgroup-a/sub1-a/sub2-a/sub3-a"},
+	{ 1, CHECK_CGROUP, 0, "/../cgroup-a/sub1-a/sub2-a/sub3-a"},
+	{ 0, CHECK_CGROUP, 1, "/../cgroup-b/sub1-b"},
+	{ 0, MOVE_CGROUP_WITH_ROOT_PREFIX, -1, "cgroup-a/sub1-a/sub2-a/sub3-a/sub4-a"},
+	{ 1, CHECK_CGROUP, 0, "/../cgroup-a/sub1-a/sub2-a/sub3-a/sub4-a"},
+	{ 0, CHECK_CGROUP, 1, "/../cgroup-b/sub1-b"},
+
+	{ 1, UNSHARE_CGROUPNS, -1, NULL},
+	{ 1, CHECK_CGROUP, 0, "/../../cgroup-a/sub1-a/sub2-a/sub3-a/sub4-a"},
+	{ 0, UNSHARE_CGROUPNS, -1, NULL},
+	{ 0, CHECK_CGROUP, 1, "/../../../../../cgroup-b/sub1-b"},
+
+	{ 0, JOIN_CGROUPNS, -1, NULL},
+	{ 1, JOIN_CGROUPNS, -1, NULL},
+
+	{ 0, CHECK_CGROUP_WITH_ROOT_PREFIX, 0, "/cgroup-a/sub1-a/sub2-a/sub3-a/sub4-a"},
+	{ 0, CHECK_CGROUP_WITH_ROOT_PREFIX, 1, "/cgroup-b/sub1-b"},
+	{ 1, CHECK_CGROUP_WITH_ROOT_PREFIX, 0, "/cgroup-a/sub1-a/sub2-a/sub3-a/sub4-a"},
+	{ 1, CHECK_CGROUP_WITH_ROOT_PREFIX, 1, "/cgroup-b/sub1-b"},
+};
+#define cgroupns_tests_len (sizeof(cgroupns_tests) / sizeof(cgroupns_tests[0]))
+
+static void
+get_cgroup_mountpoint(char *path, size_t len)
+{
+	char line[4096];
+	char dummy[4096];
+	char mountpoint[4096];
+	FILE *f;
+
+	f = fopen("/proc/self/mountinfo", "r");
+	if (!f) {
+		printf("FAIL: cannot open mountinfo\n");
+		ksft_exit_fail();
+	}
+
+	for (;;) {
+		if (!fgets(line, sizeof(line), f)) {
+			if (ferror(f)) {
+				printf("FAIL: cannot read mountinfo\n");
+				ksft_exit_fail();
+			}
+			printf("FAIL: cannot find cgroup2 mount in mountinfo\n");
+			ksft_exit_fail();
+		}
+
+		line[strcspn(line, "\n")] = 0;
+		/* 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
+		 * (1)(2)(3)   (4)   (5)      (6)      (7)   (8) (9)   (10)         (11)
+		 */
+		if (strstr(line, " - cgroup2 ") == NULL) /* (9)=cgroup2 */
+			continue;
+
+		if (sscanf(line, "%4095s %4095s %4095s %4095s %4095s", dummy, dummy, dummy, dummy, mountpoint) != 5)
+			continue;
+
+		strncpy(path, mountpoint, len);
+		path[len-1] = '\0';
+		break;
+	}
+
+	fclose(f);
+}
+
+static void
+get_cgroup(pid_t pid, char *path, size_t len)
+{
+	char proc_path[4096];
+	char line[4096];
+	FILE *f;
+
+	if (pid > 0) {
+		sprintf(proc_path, "/proc/%d/cgroup", pid);
+	} else {
+		sprintf(proc_path, "/proc/self/cgroup");
+	}
+
+	f = fopen(proc_path, "r");
+	if (!f) {
+		printf("FAIL: cannot open %s\n", proc_path);
+		ksft_exit_fail();
+	}
+
+	for (;;) {
+		if (!fgets(line, sizeof(line), f)) {
+			if (ferror(f)) {
+				printf("FAIL: cannot read %s\n", proc_path);
+				ksft_exit_fail();
+			}
+			printf("FAIL: could not parse %s\n", proc_path);
+			ksft_exit_fail();
+		}
+
+		line[strcspn(line, "\n")] = 0;
+		if (strncmp(line, "0::", 3) == 0) {
+			strncpy(path, line+3, len);
+			path[len-1] = '\0';
+			break;
+		}
+	}
+
+	fclose(f);
+}
+
+static void
+move_cgroup(pid_t target_pid, int prefix, char *cgroup)
+{
+	char knob_dir[4096];
+	char knob_path[4096];
+	char buf[128];
+	FILE *f;
+	int ret;
+
+	if (prefix) {
+		sprintf(knob_dir, "%s/%s/%s", cgroup_mountpoint, root_cgroup, cgroup);
+		sprintf(knob_path, "%s/cgroup.procs", knob_dir, cgroup);
+	} else {
+		sprintf(knob_dir, "%s/%s", cgroup_mountpoint, cgroup);
+		sprintf(knob_path, "%s/cgroup.procs", knob_dir);
+	}
+
+	mkdir(knob_dir, 0755);
+
+	sprintf(buf, "%d\n", target_pid);
+
+	f = fopen(knob_path, "w");
+	ret = fwrite(buf, strlen(buf), 1, f);
+	if (ret != 1) {
+		printf("FAIL: cannot write to %s (ret=%d)\n", knob_path, ret);
+		ksft_exit_fail();
+	}
+	fclose(f);
+}
+
+static int
+child_func(void *arg)
+{
+	uintptr_t id = (uintptr_t) arg;
+	char child_cgroup[4096];
+	char expected_cgroup[4096];
+	char process_name[128];
+	char proc_path[128];
+	int step;
+	int ret;
+	int nsfd;
+
+	for (step = 0; step < cgroupns_tests_len; step++) {
+		uint64_t counter = 0;
+		pid_t target_pid;
+
+		/* wait a signal from the parent process before starting this step */
+		ret = read(children[id].start_semfd, &counter, sizeof(counter));
+		if (ret != sizeof(counter)) {
+			printf("FAIL: cannot read semaphore\n");
+			ksft_exit_fail();
+		}
+
+		/* only one process will do this step */
+		if (cgroupns_tests[step].actor_id == id) {
+			switch (cgroupns_tests[step].action) {
+			case UNSHARE_CGROUPNS:
+				printf("child process #%lu: unshare cgroupns\n", id);
+				ret = unshare(CLONE_NEWCGROUP);
+				if (ret != 0) {
+					printf("FAIL: cannot unshare cgroupns\n");
+					ksft_exit_fail();
+				}
+				break;
+
+			case JOIN_CGROUPNS:
+				printf("child process #%lu: join parent cgroupns\n", id);
+
+				sprintf(proc_path, "/proc/%d/ns/cgroup", getppid());
+				nsfd = open(proc_path, 0);
+				ret = setns(nsfd, CLONE_NEWCGROUP);
+				if (ret != 0) {
+					printf("FAIL: cannot join cgroupns\n");
+					ksft_exit_fail();
+				}
+				close(nsfd);
+				break;
+
+			case CHECK_CGROUP:
+			case CHECK_CGROUP_WITH_ROOT_PREFIX:
+				if (cgroupns_tests[step].action == CHECK_CGROUP || strcmp(root_cgroup, "/") == 0)
+					sprintf(expected_cgroup, "%s", cgroupns_tests[step].path);
+				else if (strcmp(cgroupns_tests[step].path, "/") == 0)
+					sprintf(expected_cgroup, "%s", root_cgroup);
+				else
+					sprintf(expected_cgroup, "%s%s", root_cgroup, cgroupns_tests[step].path);
+
+				if (cgroupns_tests[step].target_id >= 0) {
+					target_pid = children[cgroupns_tests[step].target_id].pid;
+					sprintf(process_name, "#%d (pid=%d)",
+					        cgroupns_tests[step].target_id, target_pid);
+				} else {
+					target_pid = 0;
+					sprintf(process_name, "#self (pid=%d)", getpid());
+				}
+
+				printf("child process #%lu: check that process %s has cgroup %s\n",
+				       id, process_name, expected_cgroup);
+
+				get_cgroup(target_pid, child_cgroup, sizeof(child_cgroup));
+
+				if (strcmp(child_cgroup, expected_cgroup) != 0) {
+					printf("FAIL: child has cgroup %s\n", child_cgroup);
+					ksft_exit_fail();
+				}
+
+				break;
+
+			case MOVE_CGROUP:
+			case MOVE_CGROUP_WITH_ROOT_PREFIX:
+				if (cgroupns_tests[step].target_id >= 0) {
+					target_pid = children[cgroupns_tests[step].target_id].pid;
+					sprintf(process_name, "#%d (pid=%d)",
+					        cgroupns_tests[step].target_id, target_pid);
+				} else {
+					target_pid = children[id].pid;
+					sprintf(process_name, "#self (pid=%d)", target_pid);
+				}
+
+				printf("child process #%lu: move process %s to cgroup %s\n",
+				       id, process_name, cgroupns_tests[step].path);
+
+				move_cgroup(target_pid,
+				            cgroupns_tests[step].action == MOVE_CGROUP_WITH_ROOT_PREFIX,
+				            cgroupns_tests[step].path);
+				break;
+
+			default:
+				printf("FAIL: invalid action\n");
+				ksft_exit_fail();
+			}
+		}
+
+
+		/* signal the parent process we've finished this step */
+		counter = 1;
+		ret = write(children[id].end_semfd, &counter, sizeof(counter));
+		if (ret != sizeof(counter)) {
+			printf("FAIL: cannot write semaphore\n");
+			ksft_exit_fail();
+		}
+	}
+
+	return 0;
+}
+
+int
+main(int argc, char **argv)
+{
+	struct statfs fs;
+	char child_cgroup[4096];
+	int ret;
+	int status;
+	uintptr_t i;
+	int step;
+
+	get_cgroup_mountpoint(cgroup_mountpoint, sizeof(cgroup_mountpoint));
+	printf("cgroup2 mounted on: %s\n", cgroup_mountpoint);
+
+	if (statfs(cgroup_mountpoint, &fs) < 0) {
+		printf("FAIL: statfs\n");
+		ksft_exit_fail();
+	}
+
+	if (fs.f_type != (typeof(fs.f_type)) CGROUP2_SUPER_MAGIC) {
+		printf("FAIL: this test is for Linux >= 4.5 with cgroup2 mounted\n");
+		ksft_exit_fail();
+	}
+
+	get_cgroup(0, root_cgroup, sizeof(root_cgroup));
+	printf("current cgroup: %s\n", root_cgroup);
+
+	for (i = 0; i < CHILDREN_COUNT; i++) {
+		children[i].start_semfd = eventfd(0, EFD_SEMAPHORE);
+		if (children[i].start_semfd == -1) {
+			printf("FAIL: cannot create eventfd\n");
+			ksft_exit_fail();
+		}
+
+		children[i].end_semfd = eventfd(0, EFD_SEMAPHORE);
+		if (children[i].end_semfd == -1) {
+			printf("FAIL: cannot create eventfd\n");
+			ksft_exit_fail();
+		}
+
+		children[i].stack = malloc(STACK_SIZE);
+		if (!children[i].stack) {
+			printf("FAIL: cannot allocate stack\n");
+			ksft_exit_fail();
+		}
+	}
+
+	for (i = 0; i < CHILDREN_COUNT; i++) {
+		children[i].pid = clone(child_func, children[i].stack + STACK_SIZE,
+		                        SIGCHLD|CLONE_VM|CLONE_FILES, (void *)i);
+		if (children[i].pid == -1) {
+			printf("FAIL: cannot clone\n");
+			ksft_exit_fail();
+		}
+	}
+
+	for (step = 0; step < cgroupns_tests_len; step++) {
+		uint64_t counter = 1;
+
+		/* signal the child processes they can start the current step */
+		for (i = 0; i < CHILDREN_COUNT; i++) {
+			ret = write(children[i].start_semfd, &counter, sizeof(counter));
+			if (ret != sizeof(counter)) {
+				printf("FAIL: cannot write semaphore\n");
+				ksft_exit_fail();
+			}
+		}
+
+		/* wait until all child processes finished the current step */
+		for (i = 0; i < CHILDREN_COUNT; i++) {
+			ret = read(children[i].end_semfd, &counter, sizeof(counter));
+			if (ret != sizeof(counter)) {
+				printf("FAIL: cannot read semaphore\n");
+				ksft_exit_fail();
+			}
+		}
+	}
+
+	for (i = 0; i < CHILDREN_COUNT; i++) {
+		ret = waitpid(-1, &status, 0);
+		if (ret == -1 || !WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+			printf("FAIL: cannot wait child\n");
+			ksft_exit_fail();
+		}
+	}
+
+	printf("SUCCESS\n");
+	return ksft_exit_pass();
+}
-- 
2.5.0

[toc] | [prev] | [standalone]


Back to top | Article view | linux.kernel


csiph-web