[cgmanager-devel] [PATCH RFC] Implement getpidcgroupabs

Serge Hallyn serge.hallyn at ubuntu.com
Thu May 1 04:35:21 UTC 2014


This returns the cgroup path of another task relative to the
cgproxy if used, or else the full path as seen by the cgmanager.

This path can then be fed to movepidabs.  This can be used by
lxc-attach to re-attach to a container's cgroup even if the
user's new login shell is in a different cgroup session than
that under which the container was started.

Signed-off-by: Serge Hallyn <serge.hallyn at ubuntu.com>
---
 cgm                               |   9 +++
 cgmanager-proxy.c                 |  61 ++++++++++++++
 cgmanager.c                       |  33 ++++++++
 frontend.c                        | 163 ++++++++++++++++++++++++++++++++------
 frontend.h                        |   4 +
 org.linuxcontainers.cgmanager.xml |  12 +++
 tests/test22.sh                   |  38 +++++++++
 7 files changed, 295 insertions(+), 25 deletions(-)
 create mode 100644 tests/test22.sh

diff --git a/cgm b/cgm
index a614bae..f4f055a 100755
--- a/cgm
+++ b/cgm
@@ -35,6 +35,8 @@ usage() {
 	echo
 	echo "   $me getpidcgroup <controller> pid"
 	echo
+	echo "   $me getpidcgroupabs <controller> pid"
+	echo
 	echo "   $me movepid <controller> <cgroup> pid"
 	echo
 	echo "   $me movepidabs <controller> <cgroup> pid"
@@ -138,6 +140,13 @@ case "$1" in
 		dbus-send --print-reply --address=unix:path=/sys/fs/cgroup/cgmanager/sock --type=method_call /org/linuxcontainers/cgmanager org.linuxcontainers.cgmanager0_0.GetPidCgroup string:$2 int32:$3
 		exit $?
 	;;
+	getpidcgroupabs)
+		if [ $# -lt 3 ]; then
+			usage $0
+		fi
+		dbus-send --print-reply --address=unix:path=/sys/fs/cgroup/cgmanager/sock --type=method_call /org/linuxcontainers/cgmanager org.linuxcontainers.cgmanager0_0.GetPidCgroupAbs string:$2 int32:$3
+		exit $?
+	;;
 	movepid)
 		if [ $# -lt 4 ]; then
 			usage $0
diff --git a/cgmanager-proxy.c b/cgmanager-proxy.c
index 4a84018..efdeac5 100644
--- a/cgmanager-proxy.c
+++ b/cgmanager-proxy.c
@@ -304,6 +304,56 @@ out:
 	return ret;
 }
 
+int get_pid_cgroup_abs_main (void *parent, const char *controller,
+		struct ucred p, struct ucred r, struct ucred v, char **output)
+{
+	DBusMessage *message;
+	DBusMessageIter iter;
+	int sv[2], ret = -1;
+	char s[MAXPATHLEN] = { 0 };
+
+	if (memcmp(&p, &r, sizeof(struct ucred)) != 0) {
+		nih_error("%s: proxy != requestor", __func__);
+		return -1;
+	}
+
+	if (!(message = start_dbus_request("GetPidCgroupAbsScm", sv))) {
+		nih_error("%s: error starting dbus request", __func__);
+		return -1;
+	}
+
+	dbus_message_iter_init_append(message, &iter);
+	if (!dbus_message_iter_append_basic (&iter,
+			DBUS_TYPE_STRING,
+			&controller)) {
+		dbus_message_unref(message);
+		nih_error("%s: out of memory", __func__);
+		goto out;
+	}
+	if (! dbus_message_iter_append_basic (&iter, DBUS_TYPE_UNIX_FD, &sv[1])) {
+		dbus_message_unref(message);
+		nih_error("%s: out of memory", __func__);
+		goto out;
+	}
+
+	if (!complete_dbus_request(message, sv, &r, &v)) {
+		nih_error("%s: error completing dbus request", __func__);
+		goto out;
+	}
+
+	if (proxyrecv(sv[0], s, MAXPATHLEN-1) <= 0)
+		nih_error("%s: Error reading result from cgmanager",
+			__func__);
+	else {
+		*output = NIH_MUST( nih_strdup(parent, s) );
+		ret = 0;
+	}
+out:
+	close(sv[0]);
+	close(sv[1]);
+	return ret;
+}
+
 int do_move_pid_main (const char *controller, const char *cgroup,
 		struct ucred p, struct ucred r, struct ucred v,
 		const char *cmd)
@@ -377,10 +427,21 @@ int move_pid_main (const char *controller, const char *cgroup,
 int move_pid_abs_main (const char *controller, const char *cgroup,
 		struct ucred p, struct ucred r, struct ucred v)
 {
+#if 0
+	/*
+	 * We used to enforce that r must be root.  However that's
+	 * overly restrictive.
+	 * Cgmanager ensures that r must have write access to the
+	 * tasks file.  That seems sufficient.  However if it is deemed
+	 * insufficient, we can ensure that r's user or group id own
+	 * all parent directories up to a common parent, from v.cgroup
+	 * to the requested cgroup.  THIS CODE does NOT do that.
+	 */
 	if (r.uid) {
 		nih_error("%s: uid %u tried to escape", __func__, r.uid);
 		return -1;
 	}
+#endif
 	if (!sane_cgroup(cgroup)) {
 		nih_error("%s: unsafe cgroup", __func__);
 		return -1;
diff --git a/cgmanager.c b/cgmanager.c
index 6392688..cee7cf1 100644
--- a/cgmanager.c
+++ b/cgmanager.c
@@ -61,6 +61,39 @@ int get_pid_cgroup_main(void *parent, const char *controller,struct ucred p,
 	return 0;
 }
 
+/* GetPidCgroupAbs */
+int get_pid_cgroup_abs_main(void *parent, const char *controller,struct ucred p,
+			 struct ucred r, struct ucred v, char **output)
+{
+	char rcgpath[MAXPATHLEN], vcgpath[MAXPATHLEN];
+
+	// Get p's current cgroup in rcgpath
+	if (!compute_pid_cgroup(p.pid, controller, "", rcgpath, NULL)) {
+		nih_error("%s: Could not determine the requestor cgroup", __func__);
+		return -1;
+	}
+
+	// Get v's cgroup in vcgpath
+	if (!compute_pid_cgroup(v.pid, controller, "", vcgpath, NULL)) {
+		nih_error("%s: Could not determine the victim cgroup", __func__);
+		return -1;
+	}
+
+	// Make sure v's cgroup is under p's
+	int rlen = strlen(rcgpath);
+	if (strncmp(rcgpath, vcgpath, rlen) != 0) {
+		nih_error("%s: v (%d)'s cgroup is not below p (%d)'s", __func__,
+			v.pid, p.pid);
+		return -1;
+	}
+	if (strlen(vcgpath) == rlen)
+		*output = NIH_MUST (nih_strdup(parent, "/") );
+	else
+		*output = NIH_MUST (nih_strdup(parent, vcgpath + rlen) );
+
+	return 0;
+}
+
 static bool victim_under_proxy_cgroup(char *rcgpath, pid_t v,
 		const char *controller)
 {
diff --git a/frontend.c b/frontend.c
index 1dcf9c7..2740d1c 100644
--- a/frontend.c
+++ b/frontend.c
@@ -165,27 +165,6 @@ static struct scm_sock_data *alloc_scm_sock_data(NihDBusMessage *message,
 	return d;
 }
 
-#if 0
-static const char *req_type_to_str(enum req_type r)
-{
-	switch(r) {
-		case REQ_TYPE_GET_PID: return "get_pid";
-		case REQ_TYPE_MOVE_PID: return "move_pid";
-		case REQ_TYPE_MOVE_PID_ABS: return "move_pid";
-		case REQ_TYPE_CREATE: return "create";
-		case REQ_TYPE_CHOWN: return "chown";
-		case REQ_TYPE_GET_VALUE: return "get_value";
-		case REQ_TYPE_SET_VALUE: return "set_value";
-		case REQ_TYPE_REMOVE: return "remove";
-		case REQ_TYPE_GET_TASKS: return "get_tasks";
-		case REQ_TYPE_CHMOD: return "chmod";
-		case REQ_TYPE_LIST_CHILDREN: return "list_children";
-		case REQ_TYPE_REMOVE_ON_EMPTY: return "remove_on_empty";
-		default: return "invalid";
-	}
-}
-#endif
-
 /*
  * All Scm-enhanced transactions take at least one SCM cred,
  * the requestor's.  Some require a second SCM cred to identify
@@ -195,6 +174,7 @@ static bool need_two_creds(enum req_type t)
 {
 	switch (t) {
 	case REQ_TYPE_GET_PID:
+	case REQ_TYPE_GET_PID_ABS:
 	case REQ_TYPE_MOVE_PID:
 	case REQ_TYPE_MOVE_PID_ABS:
 	case REQ_TYPE_CHOWN:
@@ -208,10 +188,6 @@ static void scm_sock_error_handler (void *data, NihIo *io)
 {
 	struct scm_sock_data *d = data;
 	NihError *error = nih_error_get ();
-#if 0
-	nih_info("got an error, type %s", req_type_to_str(d->type));
-	nih_info("error %s", strerror(error->number));
-#endif
 	nih_free(error);
 	d->fd = -1;
 }
@@ -270,6 +246,7 @@ static void sock_scm_reader(struct scm_sock_data *data,
 
 	switch (data->type) {
 	case REQ_TYPE_GET_PID: get_pid_scm_complete(data); break;
+	case REQ_TYPE_GET_PID_ABS: get_pid_abs_scm_complete(data); break;
 	case REQ_TYPE_MOVE_PID: move_pid_scm_complete(data); break;
 	case REQ_TYPE_MOVE_PID_ABS: move_pid_abs_scm_complete(data); break;
 	case REQ_TYPE_CREATE: create_scm_complete(data); break;
@@ -411,6 +388,142 @@ int cgmanager_get_pid_cgroup (void *data, NihDBusMessage *message,
 	return 0;
 }
 
+void get_pid_abs_scm_complete(struct scm_sock_data *data)
+{
+	// output will be nih_alloced with data as parent, and therefore
+	// freed when data is freed.
+	char *output = NULL;
+	int ret;
+
+	ret = get_pid_cgroup_abs_main(data, data->controller, data->pcred,
+			data->rcred, data->vcred, &output);
+	if (ret == 0)
+		ret = write(data->fd, output, strlen(output)+1);
+	else
+		// Let the client know it failed
+		ret = write(data->fd, &data->rcred, 0);
+	if (ret < 0)
+		nih_error("GetPidCgroupAbsScm: Error writing final result to client: %s",
+			strerror(errno));
+}
+
+/*
+ * This is one of the dbus callbacks.
+ * Caller requests the cgroup of @pid in a given @controller, relative
+ * to the proxy's
+ */
+int cgmanager_get_pid_cgroup_abs_scm (void *data, NihDBusMessage *message,
+			const char *controller, int sockfd)
+{
+	struct scm_sock_data *d;
+
+	timeout_reset(message->connection);
+	d = alloc_scm_sock_data(message, sockfd, REQ_TYPE_GET_PID_ABS);
+	if (!d)
+		return -1;
+	d->controller = NIH_MUST( nih_strdup(d, controller) );
+
+	if (!nih_io_reopen(NULL, sockfd, NIH_IO_MESSAGE,
+				(NihIoReader) sock_scm_reader,
+				(NihIoCloseHandler) scm_sock_close,
+				scm_sock_error_handler, d)) {
+		NihError *error = nih_error_steal ();
+		nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS,
+			"Failed queue scm message: %s", error->message);
+		nih_free(error);
+		return -1;
+	}
+
+	if (!kick_fd_client(sockfd)) {
+		nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS,
+			"Error writing to client: %s", strerror(errno));
+		return -1;
+	}
+	return 0;
+}
+
+/* GetPidCgroup */
+/*
+ * This is one of the dbus callbacks.
+ * Caller requests the cgroup of @pid in a given @controller relative
+ * to the proxy's
+ */
+int cgmanager_get_pid_cgroup_abs (void *data, NihDBusMessage *message,
+			const char *controller, int plain_pid, char **output)
+{
+	int fd = 0, ret;
+	struct ucred rcred, vcred;
+	socklen_t len;
+
+	if (message == NULL) {
+		nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS,
+			"message was null");
+		return -1;
+	}
+
+	timeout_reset(message->connection);
+	if (!dbus_connection_get_socket(message->connection, &fd)) {
+		nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS,
+					     "Could not get client socket.");
+		return -1;
+	}
+
+	len = sizeof(struct ucred);
+	if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &rcred, &len) < 0) {
+		nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS,
+					     "Could not get peer cred: %s",
+					     strerror(errno));
+		return -1;
+	}
+
+	nih_info (_("GetPidCgroupAbs: Client fd is: %d (pid=%d, uid=%u, gid=%u)"),
+			fd, rcred.pid, rcred.uid, rcred.gid);
+
+	/*
+	 * getpidcgroup results cannot make sense as the pid is not
+	 * translated.  Note that on an old enough kernel we cannot detect
+	 * this situation.  In that case we allow it - it will confuse the
+	 * caller, but cause no harm
+	 */
+	if (!is_same_pidns(rcred.pid)) {
+		nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS,
+				"GetPidCgroupAbs called from non-init namespace");
+		return -1;
+	}
+	vcred.uid = 0;
+	vcred.gid = 0;
+	vcred.pid = plain_pid;
+
+#ifdef CGMANAGER
+	/*
+	 * A plain dbus request to escape cgroup root was made by a root
+	 * owned task in cgmanager's namespace.  We will send ourselves as the
+	 * proxy.
+	 */
+	struct ucred mycred = {
+		.pid = getpid(),
+		.uid = getuid(),
+		.gid = getgid()
+	};
+#else
+	/*
+	 * This is the !CGMANAGER case.  We are in the proxy.  We don't
+	 * support chained proxying anyway, so it is simple - the requestor
+	 * is the proxy at this point;  then we will proxy the call on to
+	 * the cgmanager
+	 */
+#define mycred rcred
+#endif
+
+	ret = get_pid_cgroup_abs_main(message, controller, mycred, rcred, vcred, output);
+	if (ret) {
+		nih_dbus_error_raise_printf (DBUS_ERROR_INVALID_ARGS,
+				"invalid request");
+		return -1;
+	}
+	return 0;
+}
+
 void move_pid_scm_complete(struct scm_sock_data *data)
 {
 	char b = '0';
diff --git a/frontend.h b/frontend.h
index 55b4369..20ed266 100644
--- a/frontend.h
+++ b/frontend.h
@@ -105,11 +105,15 @@ enum req_type {
 	REQ_TYPE_MOVE_PID_ABS,
 	REQ_TYPE_LIST_CHILDREN,
 	REQ_TYPE_REMOVE_ON_EMPTY,
+	REQ_TYPE_GET_PID_ABS,
 };
 
 int get_pid_cgroup_main(void *parent, const char *controller,
 		struct ucred p, struct ucred r, struct ucred v, char **output);
 void get_pid_scm_complete(struct scm_sock_data *data);
+int get_pid_cgroup_abs_main(void *parent, const char *controller,
+		struct ucred p, struct ucred r, struct ucred v, char **output);
+void get_pid_abs_scm_complete(struct scm_sock_data *data);
 int move_pid_main(const char *controller, const char *cgroup,
 		struct ucred p, struct ucred r, struct ucred v);
 void move_pid_scm_complete(struct scm_sock_data *data);
diff --git a/org.linuxcontainers.cgmanager.xml b/org.linuxcontainers.cgmanager.xml
index 4de81c5..927b666 100644
--- a/org.linuxcontainers.cgmanager.xml
+++ b/org.linuxcontainers.cgmanager.xml
@@ -44,6 +44,18 @@
       <arg name="output" type="s" direction="out" />
       <!-- client must be in manager's pidns -->
     </method>
+    <method name="GetPidCgroupAbsScm">
+      <arg name="controller" type="s" direction="in" />
+      <arg name="sockfd" type="h" direction="in" />
+      <!-- Pid is passed as a scm_cred over sockfd -->
+      <!-- Result is printed over same sockfd -->
+    </method>
+    <method name="GetPidCgroupAbs">
+      <arg name="controller" type="s" direction="in" />
+      <arg name="pid" type="i" direction="in" />
+      <arg name="output" type="s" direction="out" />
+      <!-- client must be in manager's pidns -->
+    </method>
     <method name="CreateScm">
       <arg name="controller" type="s" direction="in" />
       <arg name="cgroup" type="s" direction="in" />
diff --git a/tests/test22.sh b/tests/test22.sh
new file mode 100644
index 0000000..25652fa
--- /dev/null
+++ b/tests/test22.sh
@@ -0,0 +1,38 @@
+#!/bin/bash
+
+echo "Test 22: getpidcgroupabs"
+
+cg="test22_cg"
+
+dotest() {
+    mount --move /sys/fs/cgroup /mnt
+    mount -t tmpfs none /sys/fs/cgroup
+    mkdir /sys/fs/cgroup/cgmanager
+    touch /sys/fs/cgroup/cgmanager/sock
+    mount --bind /mnt/cgmanager/sock /sys/fs/cgroup/cgmanager/sock
+    cgproxy --debug > cgproxy.out.$$ &
+    ppid=$!
+    sleep 20 &
+    spid=$!
+    cgm create memory ab
+    dbus-send --print-reply --address=unix:path=/sys/fs/cgroup/cgmanager/sock --type=method_call /org/linuxcontainers/cgmanager org.linuxcontainers.cgmanager0_0.Create string:memory string:$cg
+    dbus-send --print-reply --address=unix:path=/sys/fs/cgroup/cgmanager/sock --type=method_call /org/linuxcontainers/cgmanager org.linuxcontainers.cgmanager0_0.MovePid string:memory string:$cg int32:$spid
+    dbus-send --print-reply --address=unix:path=/sys/fs/cgroup/cgmanager/sock --type=method_call /org/linuxcontainers/cgmanager org.linuxcontainers.cgmanager0_0.MovePid string:memory string:$cg int32:$$
+    p=`dbus-send --print-reply=literal --address=unix:path=/sys/fs/cgroup/cgmanager/sock --type=method_call /org/linuxcontainers/cgmanager org.linuxcontainers.cgmanager0_0.GetPidCgroup string:memory int32:$spid`
+    absp=`dbus-send --print-reply=literal --address=unix:path=/sys/fs/cgroup/cgmanager/sock --type=method_call /org/linuxcontainers/cgmanager org.linuxcontainers.cgmanager0_0.GetPidCgroupAbs string:memory int32:$spid`
+    kill -9 $ppid
+    kill -9 $spid
+    echo "p is .$p."
+    echo "absp is .$absp."
+    if [ "$p$cg" != "$absp" ]; then
+            echo "test 22 failed"
+            exit 1
+    fi
+}
+
+if [ $# -eq 1 ]; then
+    dotest
+    echo "test 22 passed"
+else
+    unshare -m $0 unshared
+fi
-- 
1.9.1



More information about the cgmanager-devel mailing list