Add ability to put jailed process in cgroups

This adds an API that allows the jailed process to be added to a given
cgroup.  This API can be called repeatedly to add the process to many
cgroups.  The process will be added after fork but before it is exec'd.

BUG=b/26549867
TEST=set cgroups and inspect that pid is in tasks file

Change-Id: I87a9897c1dc741c726873e872eeae32692088979
Signed-off-by: Dylan Reid <dgreid@chromium.org>
diff --git a/libminijail.c b/libminijail.c
index e1d5cb5..b42ac72 100644
--- a/libminijail.c
+++ b/libminijail.c
@@ -72,6 +72,8 @@
 # define SECCOMP_SOFTFAIL 0
 #endif
 
+#define MAX_CGROUPS 10 /* 10 different controllers supported by Linux. */
+
 struct mountpoint {
 	char *src;
 	char *dest;
@@ -131,6 +133,8 @@
 	struct mountpoint *mounts_head;
 	struct mountpoint *mounts_tail;
 	size_t mounts_count;
+	char *cgroups[MAX_CGROUPS];
+	size_t cgroup_count;
 };
 
 /*
@@ -530,6 +534,17 @@
 	return 0;
 }
 
+int API minijail_add_to_cgroup(struct minijail *j, const char *path)
+{
+	if (j->cgroup_count >= MAX_CGROUPS)
+		return -ENOMEM;
+	j->cgroups[j->cgroup_count] = strdup(path);
+	if (!j->cgroups[j->cgroup_count])
+		return -ENOMEM;
+	j->cgroup_count++;
+	return 0;
+}
+
 int API minijail_mount(struct minijail *j, const char *src, const char *dest,
 		       const char *type, unsigned long flags)
 {
@@ -658,6 +673,8 @@
 			     const struct minijail *j)
 {
 	struct mountpoint *m = NULL;
+	size_t i;
+
 	marshal_append(state, (char *)j, sizeof(*j));
 	if (j->user)
 		marshal_append(state, j->user, strlen(j->user) + 1);
@@ -682,6 +699,8 @@
 		marshal_append(state, m->type, strlen(m->type) + 1);
 		marshal_append(state, (char *)&m->flags, sizeof(m->flags));
 	}
+	for (i = 0; i < j->cgroup_count; ++i)
+		marshal_append(state, j->cgroups[i], strlen(j->cgroups[i]) + 1);
 }
 
 size_t API minijail_size(const struct minijail *j)
@@ -840,8 +859,31 @@
 			goto bad_mounts;
 	}
 
+	count = j->cgroup_count;
+	j->cgroup_count = 0;
+	for (i = 0; i < count; ++i) {
+		char *cgroup = consumestr(&serialized, &length);
+		if (!cgroup)
+			goto bad_cgroups;
+		j->cgroups[i] = strdup(cgroup);
+		if (!j->cgroups[i])
+			goto bad_cgroups;
+		++j->cgroup_count;
+	}
+
 	return 0;
 
+bad_cgroups:
+	while (j->mounts_head) {
+		struct mountpoint *m = j->mounts_head;
+		j->mounts_head = j->mounts_head->next;
+		free(m->type);
+		free(m->dest);
+		free(m->src);
+		free(m);
+	}
+	for (i = 0; i < j->cgroup_count; ++i)
+		free(j->cgroups[i]);
 bad_mounts:
 	if (j->flags.seccomp_filter && j->filter_len > 0) {
 		free(j->filter_prog->filter);
@@ -867,6 +909,7 @@
 	j->suppl_gid_list = NULL;
 	j->chrootdir = NULL;
 	j->alt_syscall_table = NULL;
+	j->cgroup_count = 0;
 out:
 	return ret;
 }
@@ -1069,16 +1112,29 @@
 	return 0;
 }
 
-static void write_pid_file(const struct minijail *j)
+static void write_pid_to_path(pid_t pid, const char *path)
 {
-	FILE *fp = fopen(j->pid_file_path, "w");
+	FILE *fp = fopen(path, "w");
 
 	if (!fp)
-		pdie("failed to open '%s'", j->pid_file_path);
-	if (fprintf(fp, "%d\n", (int)j->initpid) < 0)
-		pdie("fprintf(%s)", j->pid_file_path);
+		pdie("failed to open '%s'", path);
+	if (fprintf(fp, "%d\n", (int)pid) < 0)
+		pdie("fprintf(%s)", path);
 	if (fclose(fp))
-		pdie("fclose(%s)", j->pid_file_path);
+		pdie("fclose(%s)", path);
+}
+
+static void write_pid_file(const struct minijail *j)
+{
+	write_pid_to_path(j->initpid, j->pid_file_path);
+}
+
+static void assign_cgroups(const struct minijail *j)
+{
+	size_t i;
+
+	for (i = 0; i < j->cgroup_count; ++i)
+		write_pid_to_path(j->initpid, j->cgroups[i]);
 }
 
 void drop_ugid(const struct minijail *j)
@@ -1634,7 +1690,7 @@
 	 * If we want to set up a new uid/gid mapping in the user namespace,
 	 * create the pipe(2) to sync between parent and child.
 	 */
-	if (j->flags.userns) {
+	if (j->flags.userns || j->cgroup_count) {
 		sync_child = 1;
 		if (pipe(child_sync_pipe_fds))
 			return -EFAULT;
@@ -1714,6 +1770,8 @@
 		if (j->flags.pid_file)
 			write_pid_file(j);
 
+		assign_cgroups(j);
+
 		if (j->flags.userns)
 			write_ugid_mappings(j);
 
@@ -1897,6 +1955,8 @@
 
 void API minijail_destroy(struct minijail *j)
 {
+	size_t i;
+
 	if (j->flags.seccomp_filter && j->filter_prog) {
 		free(j->filter_prog->filter);
 		free(j->filter_prog);
@@ -1918,5 +1978,7 @@
 		free(j->chrootdir);
 	if (j->alt_syscall_table)
 		free(j->alt_syscall_table);
+	for (i = 0; i < j->cgroup_count; ++i)
+		free(j->cgroups[i]);
 	free(j);
 }