From beeefb214a2dc8917b5a31945e740ecce4536764 Mon Sep 17 00:00:00 2001
From: Stefano Brivio <sbrivio@redhat.com>
Date: Wed, 28 Jun 2023 17:45:36 +0200
Subject: cooker, seitan: Add support for GID/UID in context

Similarly to namespace specifications, the special value "caller", as
well as login/group names and numeric UID/GIDs are supported.

Example of usage in demo/mknod.hjson. Light on checks and with some
TODOs left behind at the moment.

Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
---
 common/gluten.h  | 29 ++++++++++++++++++++---------
 common/util.c    |  7 ++++++-
 cooker/call.c    | 19 +++++++++++++++----
 demo/mknod.hjson |  2 +-
 operations.c     | 55 +++++++++++++++++++++++++++++++++++++++++++++++++------
 operations.h     |  2 ++
 6 files changed, 93 insertions(+), 21 deletions(-)

diff --git a/common/gluten.h b/common/gluten.h
index 185927a..f3ef47d 100644
--- a/common/gluten.h
+++ b/common/gluten.h
@@ -12,6 +12,7 @@
 #include <stdbool.h>
 #include <sys/types.h>
 #include <linux/limits.h>
+#include <limits.h>
 #include <linux/seccomp.h>
 
 #include <stdio.h>
@@ -75,23 +76,25 @@ enum op_type {
 };
 
 /**
- * enum context_spec_type - Type of reference to target namespace and directory
+ * enum context_spec_type - Type of reference to namespace, directory, UID/GID
  */
 enum context_spec_type {
 	CONTEXT_SPEC_NONE		= 0,
 
-	/* PID from seccomp_data */
+	/* PID from seccomp_data, UID/GID resolved via procfs in seitan */
 	CONTEXT_SPEC_CALLER		= 1,
 
-	/* PID/path from gluten, resolved in seitan */
-	CONTEXT_SPEC_PID		= 2,
-	CONTEXT_SPEC_PATH		= 3,
+	/* UID/GID, or PID resolved in seitan */
+	CONTEXT_SPEC_NUM		= 2,
 
-	CONTEXT_SPEC_TYPE_MAX		= CONTEXT_SPEC_PATH,
+	/* User/group names or namespace path, resolved in seitan */
+	CONTEXT_SPEC_NAME		= 3,
+
+	CONTEXT_SPEC_TYPE_MAX		= CONTEXT_SPEC_NAME,
 };
 
 /**
- * enum context_type - Working directory, and namespaces (see <linux/sched.h>)
+ * enum context_type - Directory, namespaces (see <linux/sched.h>), UID/GID
  */
 enum context_type {
 	NS_MOUNT		= 0,
@@ -104,17 +107,22 @@ enum context_type {
 	NS_TIME			= 7,
 	NS_TYPE_MAX		= NS_TIME,
 	CWD			= 8,
-	CONTEXT_TYPE_MAX	= CWD,
+	UID			= 9,
+	GID			= 10,
+	CONTEXT_TYPE_MAX	= GID,
 };
 
 extern const char *context_type_name[CONTEXT_TYPE_MAX + 1];
 extern const char *context_spec_type_name[CONTEXT_SPEC_TYPE_MAX + 1];
 /**
  * struct context_desc - Identification of one type of context information
- * @context:		Type of context (namespace types, or working directory)
+ * @context:		Type of context (namespace, working directory, UID/GID)
  * @spec:		Reference type
  * @target.pid:		PID in procfs reference
+ * @target.uid:		UID to switch to
+ * @target.gid:		GID to switch to
  * @target.path:	Filesystem-bound (nsfs) reference
+ * @target.name:	Username or group name
  */
 struct context_desc {
 #ifdef __GNUC__
@@ -126,7 +134,10 @@ struct context_desc {
 #endif
 	union {
 		pid_t pid;
+		uid_t uid;
+		gid_t gid;
 		char path[PATH_MAX];
+		char name[LOGIN_NAME_MAX];
 	} target;
 };
 
diff --git a/common/util.c b/common/util.c
index 21676b0..d74e199 100644
--- a/common/util.c
+++ b/common/util.c
@@ -8,6 +8,9 @@
  * Author: Stefano Brivio <sbrivio@redhat.com>
  */
 
+#include <bits/local_lim.h>	/* TODO: Why isn't __USE_POSIX with limits.h
+				 * enough for LOGIN_NAME_MAX here?
+				 */
 #include <stdio.h>
 #include <stdarg.h>
 #include <string.h>
@@ -35,7 +38,9 @@ const char *gluten_offset_name[OFFSET_TYPE_MAX + 1] = {
 };
 
 const char *context_type_name[CONTEXT_TYPE_MAX + 1] = {
-	"mnt", "cgroup", "uts", "ipc", "user", "pid", "net", "time", "cwd",
+	"mnt", "cgroup", "uts", "ipc", "user", "pid", "net", "time",
+	"cwd",
+	"uid", "gid",
 };
 
 const char *context_spec_type_name[CONTEXT_SPEC_TYPE_MAX + 1] = {
diff --git a/cooker/call.c b/cooker/call.c
index c3f290c..6dbfd29 100644
--- a/cooker/call.c
+++ b/cooker/call.c
@@ -458,14 +458,25 @@ static void parse_context(struct context_desc *cdesc, JSON_Object *obj)
 			if (!strcmp(str, "caller")) {
 				cdesc[n].spec = CONTEXT_SPEC_CALLER;
 			} else {
-				cdesc[n].spec = CONTEXT_SPEC_PATH;
-				strncpy(cdesc[n].target.path, str, PATH_MAX);
+				cdesc[n].spec = CONTEXT_SPEC_NAME;
+				if (type == UID || type == GID) {
+					strncpy(cdesc[n].target.name, str,
+						LOGIN_NAME_MAX);
+				} else {
+					strncpy(cdesc[n].target.path, str,
+						PATH_MAX);
+				}
 			}
 		} else if ((num = json_object_get_number(obj, name))) {
 			debug("   '%s' context: %lli", name, num);
 
-			cdesc[n].spec = CONTEXT_SPEC_PID;
-			cdesc[n].target.pid = num;
+			cdesc[n].spec = CONTEXT_SPEC_NUM;
+			if (type == UID)
+				cdesc[n].target.uid = num;
+			else if (type == GID)
+				cdesc[n].target.gid = num;
+			else
+				cdesc[n].target.pid = num;
 		} else {
 			die("invalid context specification");
 		}
diff --git a/demo/mknod.hjson b/demo/mknod.hjson
index 9124a8a..1936eb8 100644
--- a/demo/mknod.hjson
+++ b/demo/mknod.hjson
@@ -26,7 +26,7 @@
           "major": 1,
           "minor": { "tag": { "get": "minor" } }
         },
-        "context": { "mnt": "caller" }
+        "context": { "mnt": "caller", "uid": "caller", "gid": "caller" }
       },
     "return": { "value": 0, "error": 0 }
   }
diff --git a/operations.c b/operations.c
index 97c4730..5a882d9 100644
--- a/operations.c
+++ b/operations.c
@@ -14,11 +14,13 @@
 #include <stdlib.h>
 #include <string.h>
 #include <fcntl.h>
-#include <limits.h>
 #include <sched.h>
 #include <unistd.h>
 #include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
 #include <sys/wait.h>
+#include <pwd.h>
 #include <linux/seccomp.h>
 #include <linux/filter.h>
 #include <linux/audit.h>
@@ -79,6 +81,7 @@ static int write_syscall_ret(struct gluten *g, struct syscall_desc *s,
 	return 0;
 }
 
+/* TODO: Move all "context" stuff to separate file */
 static int prepare_arg_clone(const struct seccomp_notif *req, struct gluten *g,
 			     struct syscall_desc *s, struct context_desc *cdesc,
 			     struct arg_clone *c)
@@ -130,26 +133,57 @@ static int prepare_arg_clone(const struct seccomp_notif *req, struct gluten *g,
 			if (type == CWD) {
 				snprintf(c->cwd, PATH_MAX, "/proc/%d/root",
 					 req->pid);
+			} else if (type == UID || type == GID) {
+				/* TODO: Move into its own function */
+				struct stat st = { 0 };
+				char path[PATH_MAX];
+
+				snprintf(path, PATH_MAX, "/proc/%d", req->pid);
+				if (stat(path, &st))
+					return errno;
+
+				if (type == UID)
+					c->uid = st.st_uid;
+				else if (type == GID)
+					c->gid = st.st_gid;
 			} else {
 				snprintf(*dst, PATH_MAX, "/proc/%d/ns/%s",
-					req->pid, context_type_name[type]);
+					 req->pid, context_type_name[type]);
 			}
+
 			break;
-		case CONTEXT_SPEC_PID:
+		case CONTEXT_SPEC_NUM:
 			if (type == CWD) {
 				snprintf(c->cwd, PATH_MAX, "/proc/%d/root",
 					 cdesc->target.pid);
+			} else if (type == UID) {
+				c->uid = cdesc->target.uid;
+			} else if (type == GID) {
+				c->gid = cdesc->target.gid;
 			} else {
 				snprintf(*dst, PATH_MAX, "/proc/%d/ns/%s",
 					 cdesc->target.pid,
 					 context_type_name[type]);
 			}
+
 			break;
-		case CONTEXT_SPEC_PATH:
-			if (type == CWD)
+		case CONTEXT_SPEC_NAME:
+			if (type == CWD) {
 				strncpy(c->cwd, cdesc->target.path, PATH_MAX);
-			else 
+			} else if (type == UID || type == GID) {
+				struct passwd *pw;
+
+				if (!(pw = getpwnam(cdesc->target.name)))
+					return errno;
+
+				if (type == UID)
+					c->uid = pw->pw_uid;
+				else if (type == GID)
+					c->gid = pw->pw_gid;
+			} else {
 				strncpy(*dst, cdesc->target.path, PATH_MAX);
+			}
+
 			break;
 		default:
 			break;
@@ -183,6 +217,15 @@ static int execute_syscall(void *args)
 {
 	struct arg_clone *c = (struct arg_clone *)args;
 
+	/* We can use 0 as "unspecified" value because we can't switch from a
+	 * non-zero UID/GID to zero.
+	 */
+	if (c->uid && setuid(c->uid))
+		exit(EXIT_FAILURE);
+
+	if (c->gid && setgid(c->gid))
+		exit(EXIT_FAILURE);
+
 	if (*c->cwd && chdir(c->cwd) < 0)
 		exit(EXIT_FAILURE);
 
diff --git a/operations.h b/operations.h
index 9f2e2d6..6cff7a8 100644
--- a/operations.h
+++ b/operations.h
@@ -37,6 +37,8 @@ struct arg_clone {
 	const void *args[6];
 	char ns_path[NS_TYPE_MAX + 1][PATH_MAX];
 	char cwd[PATH_MAX];
+	uid_t uid;
+	gid_t gid;
 	long ret;
 	int err;
 };
-- 
cgit v1.2.3