public inbox for passt-dev@passt.top
 help / color / mirror / code / Atom feed
From: David Gibson <david@gibson.dropbear.id.au>
To: passt-dev@passt.top
Subject: [PATCH v2 20/32] Handle userns isolation and dropping root at the same time
Date: Thu, 08 Sep 2022 16:16:09 +1000	[thread overview]
Message-ID: <20220908061621.2430844-21-david@gibson.dropbear.id.au> (raw)
In-Reply-To: <20220908061621.2430844-1-david@gibson.dropbear.id.au>

[-- Attachment #1: Type: text/plain, Size: 12918 bytes --]

passt/pasta can interact with user namespaces in a number of ways:
   1) With --netns-only we'll remain in our original user namespace
   2) With --userns or a PID option to pasta we'll join either the given
      user namespace or that of the PID
   3) When pasta spawns a shell or command we'll start a new user namespace
      for the command and then join it
   4) With passt we'll create a new user namespace when we sandbox()
      ourself

However (3) and (4) turn out to have essentially the same effect.  In both
cases we create one new user namespace.  The spawned command starts there,
and passt/pasta itself will live there from sandbox() onwards.

Because of this, we can simplify user namespace handling by moving the
userns handling earlier, to the same point we drop root in the original
namespace.  Extend the drop_user() function to isolate_user() which does
both.

After switching UID and GID in the original userns, isolate_user() will
either join or create the userns we require.  When we spawn a command with
pasta_start_ns()/pasta_setup_ns() we no longer need to create a userns,
because we're already made one.  sandbox() likewise no longer needs to
create (or join) an userns because we're already in the one we need.

We no longer need c->pasta_userns_fd, since the fd is only used locally
in isolate_user().  Likewise we can replace c->netns_only with a local
in conf(), since it's not used outside there.

Signed-off-by: David Gibson <david(a)gibson.dropbear.id.au>
---
 conf.c      | 11 ++++----
 isolation.c | 75 ++++++++++++++++++++++++++++++++++++++++++-----------
 isolation.h |  2 +-
 passt.c     |  3 ++-
 passt.h     |  4 ---
 pasta.c     | 56 +++++----------------------------------
 pasta.h     |  2 +-
 util.c      |  5 ----
 8 files changed, 77 insertions(+), 81 deletions(-)

diff --git a/conf.c b/conf.c
index 7171748..27d520e 100644
--- a/conf.c
+++ b/conf.c
@@ -1043,6 +1043,7 @@ static int conf_ugid(const char *runas, uid_t *uid, gid_t *gid)
  */
 void conf(struct ctx *c, int argc, char **argv)
 {
+	int netns_only = 0;
 	struct option options[] = {
 		{"debug",	no_argument,		NULL,		'd' },
 		{"quiet",	no_argument,		NULL,		'q' },
@@ -1077,7 +1078,7 @@ void conf(struct ctx *c, int argc, char **argv)
 		{"udp-ns",	required_argument,	NULL,		'U' },
 		{"userns",	required_argument,	NULL,		2 },
 		{"netns",	required_argument,	NULL,		3 },
-		{"netns-only",	no_argument,		&c->netns_only,	1 },
+		{"netns-only",	no_argument,		&netns_only,	1 },
 		{"config-net",	no_argument,		&c->pasta_conf_ns, 1 },
 		{"ns-mac-addr",	required_argument,	NULL,		4 },
 		{"dhcp-dns",	no_argument,		NULL,		5 },
@@ -1515,22 +1516,22 @@ void conf(struct ctx *c, int argc, char **argv)
 	if (ret)
 		usage(argv[0]);
 
-	drop_root(uid, gid);
-
 	if (c->mode == MODE_PASTA) {
-		if (conf_pasta_ns(&c->netns_only, userns, netns,
+		if (conf_pasta_ns(&netns_only, userns, netns,
 				  optind, argc, argv) < 0)
 			usage(argv[0]);
 	} else if (optind != argc) {
 		usage(argv[0]);
 	}
 
+	isolate_user(uid, gid, !netns_only, userns);
+
 	if (c->pasta_conf_ns)
 		c->no_ra = 1;
 
 	if (c->mode == MODE_PASTA) {
 		if (*netns) {
-			pasta_open_ns(c, userns, netns);
+			pasta_open_ns(c, netns);
 		} else {
 			pasta_start_ns(c, argc - optind, argv + optind);
 		}
diff --git a/isolation.c b/isolation.c
index bc8240f..f14b154 100644
--- a/isolation.c
+++ b/isolation.c
@@ -20,6 +20,7 @@
 #include <limits.h>
 #include <pwd.h>
 #include <sched.h>
+#include <stdbool.h>
 #include <stddef.h>
 #include <stdlib.h>
 #include <string.h>
@@ -59,12 +60,19 @@ void drop_caps(void)
 }
 
 /**
- * drop_root() - Switch to given UID and GID
+ * isolate_user() - Switch to final UID/GID and move into userns
+ * @uid:	UID to run as (in original userns)
+ * @gid:	GID to run as (in original userns)
+ * @use_userns:	Whether to join or create a userns
+ * @userns:	userns path to enter, may be empty
  */
-void drop_root(uid_t uid, gid_t gid)
+void isolate_user(uid_t uid, gid_t gid, bool use_userns, const char *userns)
 {
+	char nsmap[BUFSIZ];
+
+	/* First set our UID & GID in the original namespace */
 	if (setgroups(0, NULL)) {
-		/* If we don't start with CAP_SETGID, this will EPERM */
+		/* If we don't have CAP_SETGID, this will EPERM */
 		if (errno != EPERM) {
 			err("Can't drop supplementary groups: %s",
 			    strerror(errno));
@@ -72,11 +80,57 @@ void drop_root(uid_t uid, gid_t gid)
 		}
 	}
 
-	if (!setgid(gid) && !setuid(uid))
+	if (setgid(gid) != 0) {
+		err("Can't set GID to %u: %s", gid, strerror(errno));
+		exit(EXIT_FAILURE);
+	}
+
+	if (setuid(uid) != 0) {
+		err("Can't set UID to %u: %s", uid, strerror(errno));
+		exit(EXIT_FAILURE);
+	}
+
+	/* If we're told not to use a userns, nothing more to do */
+	if (!use_userns)
+		return;
+
+	/* Otherwise, if given a userns, join it */
+	if (*userns) {
+		int ufd;
+
+		ufd = open(userns, O_RDONLY | O_CLOEXEC);
+		if (ufd < 0) {
+			err("Couldn't open user namespace %s: %s",
+			    userns, strerror(errno));
+			exit(EXIT_FAILURE);
+		}
+
+		if (setns(ufd, CLONE_NEWUSER) != 0) {
+			err("Couldn't enter user namespace %s: %s",
+			    userns, strerror(errno));
+			exit(EXIT_FAILURE);
+		}
+
+		close(ufd);
+
 		return;
+	}
 
-	err("Can't change user/group, exiting");
-	exit(EXIT_FAILURE);
+	/* Otherwise, create our own userns */
+	if (unshare(CLONE_NEWUSER) != 0) {
+		err("Couldn't create user namespace: %s", strerror(errno));
+		exit(EXIT_FAILURE);
+	}
+
+	/* Configure user and group mappings */
+	snprintf(nsmap, BUFSIZ, "0 %u 1", uid);
+	FWRITE("/proc/self/uid_map", nsmap, "Cannot set uid_map in namespace");
+
+	FWRITE("/proc/self/setgroups", "deny",
+	       "Cannot write to setgroups in namespace");
+
+	snprintf(nsmap, BUFSIZ, "0 %u 1", gid);
+	FWRITE("/proc/self/gid_map", nsmap, "Cannot set gid_map in namespace");
 }
 
 /**
@@ -88,15 +142,6 @@ int sandbox(struct ctx *c)
 {
 	int flags = CLONE_NEWIPC | CLONE_NEWNS | CLONE_NEWUTS;
 
-	if (!c->netns_only) {
-		if (c->pasta_userns_fd == -1)
-			flags |= CLONE_NEWUSER;
-		else
-			setns(c->pasta_userns_fd, CLONE_NEWUSER);
-	}
-
-	c->pasta_userns_fd = -1;
-
 	/* If we run in foreground, we have no chance to actually move to a new
 	 * PID namespace. For passt, use CLONE_NEWPID anyway, in case somebody
 	 * ever gets around seccomp profiles -- there's no harm in passing it.
diff --git a/isolation.h b/isolation.h
index 2540a35..2c73df7 100644
--- a/isolation.h
+++ b/isolation.h
@@ -8,7 +8,7 @@
 #define ISOLATION_H
 
 void drop_caps(void);
-void drop_root(uid_t uid, gid_t gid);
+void isolate_user(uid_t uid, gid_t gid, bool use_userns, const char *userns);
 int sandbox(struct ctx *c);
 void seccomp(const struct ctx *c);
 
diff --git a/passt.c b/passt.c
index 2a8314c..f0ed897 100644
--- a/passt.c
+++ b/passt.c
@@ -23,6 +23,7 @@
 #include <fcntl.h>
 #include <sys/mman.h>
 #include <sys/resource.h>
+#include <stdbool.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <netdb.h>
@@ -185,7 +186,7 @@ int main(int argc, char **argv)
 
 	drop_caps();
 
-	c.pasta_userns_fd = c.pasta_netns_fd = c.fd_tap = c.fd_tap_listen = -1;
+	c.pasta_netns_fd = c.fd_tap = c.fd_tap_listen = -1;
 
 	sigemptyset(&sa.sa_mask);
 	sa.sa_flags = 0;
diff --git a/passt.h b/passt.h
index 3035430..8c8d710 100644
--- a/passt.h
+++ b/passt.h
@@ -145,8 +145,6 @@ struct ip6_ctx {
  * @pcap:		Path for packet capture file
  * @pid_file:		Path to PID file, empty string if not configured
  * @pasta_netns_fd:	File descriptor for network namespace in pasta mode
- * @pasta_userns_fd:	Descriptor for user namespace to join, -1 once joined
- * @netns_only:		In pasta mode, don't join or create a user namespace
  * @no_netns_quit:	In pasta mode, don't exit if fs-bound namespace is gone
  * @netns_base:		Base name for fs-bound namespace, if any, in pasta mode
  * @netns_dir:		Directory of fs-bound namespace, if any, in pasta mode
@@ -197,8 +195,6 @@ struct ctx {
 	char pid_file[PATH_MAX];
 
 	int pasta_netns_fd;
-	int pasta_userns_fd;
-	int netns_only;
 
 	int no_netns_quit;
 	char netns_base[PATH_MAX];
diff --git a/pasta.c b/pasta.c
index 7eac8e9..e233762 100644
--- a/pasta.c
+++ b/pasta.c
@@ -23,6 +23,7 @@
 #include <libgen.h>
 #include <limits.h>
 #include <stdlib.h>
+#include <stdbool.h>
 #include <stdint.h>
 #include <unistd.h>
 #include <syslog.h>
@@ -83,16 +84,6 @@ static int pasta_wait_for_ns(void *arg)
 	int flags = O_RDONLY | O_CLOEXEC;
 	char ns[PATH_MAX];
 
-	if (c->netns_only)
-		goto netns;
-
-	snprintf(ns, PATH_MAX, "/proc/%i/ns/user", pasta_child_pid);
-	do
-		while ((c->pasta_userns_fd = open(ns, flags)) < 0);
-	while (setns(c->pasta_userns_fd, CLONE_NEWUSER) &&
-	       !close(c->pasta_userns_fd));
-
-netns:
 	snprintf(ns, PATH_MAX, "/proc/%i/ns/net", pasta_child_pid);
 	do
 		while ((c->pasta_netns_fd = open(ns, flags)) < 0);
@@ -112,25 +103,23 @@ static int ns_check(void *arg)
 {
 	struct ctx *c = (struct ctx *)arg;
 
-	if ((!c->netns_only && setns(c->pasta_userns_fd, CLONE_NEWUSER)) ||
-	    setns(c->pasta_netns_fd, CLONE_NEWNET))
-		c->pasta_userns_fd = c->pasta_netns_fd = -1;
+	if (setns(c->pasta_netns_fd, CLONE_NEWNET))
+		c->pasta_netns_fd = -1;
 
 	return 0;
 
 }
 
 /**
- * pasta_open_ns() - Open network, user namespaces descriptors
+ * pasta_open_ns() - Open network namespace descriptors
  * @c:		Execution context
- * @userns:	--userns argument, can be an empty string
  * @netns:	network namespace path
  *
  * Return: 0 on success, negative error code otherwise
  */
-void pasta_open_ns(struct ctx *c, const char *userns, const char *netns)
+void pasta_open_ns(struct ctx *c, const char *netns)
 {
-	int ufd = -1, nfd = -1;
+	int nfd = -1;
 
 	nfd = open(netns, O_RDONLY | O_CLOEXEC);
 	if (nfd < 0) {
@@ -138,17 +127,7 @@ void pasta_open_ns(struct ctx *c, const char *userns, const char *netns)
 		exit(EXIT_FAILURE);
 	}
 
-	if (!c->netns_only && *userns) {
-		ufd = open(userns, O_RDONLY | O_CLOEXEC);
-		if (ufd < 0) {
-			close(nfd);
-			err("Couldn't open user namespace %s", userns);
-			exit(EXIT_FAILURE);
-		}
-	}
-
 	c->pasta_netns_fd = nfd;
-	c->pasta_userns_fd = ufd;
 
 	NS_CALL(ns_check, c);
 
@@ -169,12 +148,9 @@ void pasta_open_ns(struct ctx *c, const char *userns, const char *netns)
 
 /**
  * struct pasta_setup_ns_arg - Argument for pasta_setup_ns()
- * @c:		Execution context
- * @euid:	Effective UID of caller
+ * @argv:	Command and arguments to run
  */
 struct pasta_setup_ns_arg {
-	struct ctx *c;
-	int euid;
 	char **argv;
 };
 
@@ -188,21 +164,6 @@ static int pasta_setup_ns(void *arg)
 {
 	struct pasta_setup_ns_arg *a = (struct pasta_setup_ns_arg *)arg;
 
-	if (!a->c->netns_only) {
-		char buf[BUFSIZ];
-
-		snprintf(buf, BUFSIZ, "%i %i %i", 0, a->euid, 1);
-
-		FWRITE("/proc/self/uid_map", buf,
-		       "Cannot set uid_map in namespace");
-
-		FWRITE("/proc/self/setgroups", "deny",
-		       "Cannot write to setgroups in namespace");
-
-		FWRITE("/proc/self/gid_map", buf,
-		       "Cannot set gid_map in namespace");
-	}
-
 	FWRITE("/proc/sys/net/ipv4/ping_group_range", "0 0",
 	       "Cannot set ping_group_range, ICMP requests might fail");
 
@@ -221,8 +182,6 @@ static int pasta_setup_ns(void *arg)
 void pasta_start_ns(struct ctx *c, int argc, char *argv[])
 {
 	struct pasta_setup_ns_arg arg = {
-		.c = c,
-		.euid = geteuid(),
 		.argv = argv,
 	};
 	char *shell = getenv("SHELL") ? getenv("SHELL") : "/bin/sh";
@@ -244,7 +203,6 @@ void pasta_start_ns(struct ctx *c, int argc, char *argv[])
 
 	pasta_child_pid = clone(pasta_setup_ns,
 				ns_fn_stack + sizeof(ns_fn_stack) / 2,
-				(c->netns_only ? 0 : CLONE_NEWUSER) |
 				CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNET |
 				CLONE_NEWUTS,
 				(void *)&arg);
diff --git a/pasta.h b/pasta.h
index a1937b2..02df1f6 100644
--- a/pasta.h
+++ b/pasta.h
@@ -6,7 +6,7 @@
 #ifndef PASTA_H
 #define PASTA_H
 
-void pasta_open_ns(struct ctx *c, const char *userns, const char *netns);
+void pasta_open_ns(struct ctx *c, const char *netns);
 void pasta_start_ns(struct ctx *c, int argc, char *argv[]);
 void pasta_ns_conf(struct ctx *c);
 void pasta_child_handler(int signal);
diff --git a/util.c b/util.c
index f709838..8d26561 100644
--- a/util.c
+++ b/util.c
@@ -464,11 +464,6 @@ void procfs_scan_listen(struct ctx *c, uint8_t proto, int ip_version, int ns,
  */
 int ns_enter(const struct ctx *c)
 {
-	if (!c->netns_only &&
-	    c->pasta_userns_fd != -1 &&
-	    setns(c->pasta_userns_fd, CLONE_NEWUSER))
-		exit(EXIT_FAILURE);
-
 	if (setns(c->pasta_netns_fd, CLONE_NEWNET))
 		exit(EXIT_FAILURE);
 
-- 
@@ -464,11 +464,6 @@ void procfs_scan_listen(struct ctx *c, uint8_t proto, int ip_version, int ns,
  */
 int ns_enter(const struct ctx *c)
 {
-	if (!c->netns_only &&
-	    c->pasta_userns_fd != -1 &&
-	    setns(c->pasta_userns_fd, CLONE_NEWUSER))
-		exit(EXIT_FAILURE);
-
 	if (setns(c->pasta_netns_fd, CLONE_NEWNET))
 		exit(EXIT_FAILURE);
 
-- 
2.37.3


  parent reply	other threads:[~2022-09-08  6:16 UTC|newest]

Thread overview: 33+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-09-08  6:15 [PATCH v2 00/32] Improve handling of test temporary files David Gibson
2022-09-08  6:15 ` [PATCH v2 01/32] test: Correctly match "background" with "wait" commands David Gibson
2022-09-08  6:15 ` [PATCH v2 02/32] test: Context execution helpers David Gibson
2022-09-08  6:15 ` [PATCH v2 03/32] test: Allow a tmux pane to watch commands executed in contexts David Gibson
2022-09-08  6:15 ` [PATCH v2 04/32] test: Integration of old-style pane execution and new context execution David Gibson
2022-09-08  6:15 ` [PATCH v2 05/32] test: Issue host commands via context for most tests David Gibson
2022-09-08  6:15 ` [PATCH v2 06/32] test: Use new-style contexts for passt pane in the pasta and passt tests David Gibson
2022-09-08  6:15 ` [PATCH v2 07/32] test: Add nsholder utility David Gibson
2022-09-08  6:15 ` [PATCH v2 08/32] test: Extend context system to run commands in namespace for pasta tests David Gibson
2022-09-08  6:15 ` [PATCH v2 09/32] test: Use context system for guest commands David Gibson
2022-09-08  6:15 ` [PATCH v2 10/32] test: Use context system for two_guests tests David Gibson
2022-09-08  6:16 ` [PATCH v2 11/32] test: Use new-style command issue for passt_in_ns tests David Gibson
2022-09-08  6:16 ` [PATCH v2 12/32] Don't store UID & GID persistently in the context structure David Gibson
2022-09-08  6:16 ` [PATCH v2 13/32] Split checking for root from dropping root privilege David Gibson
2022-09-08  6:16 ` [PATCH v2 14/32] Consolidate determination of UID/GID to run as David Gibson
2022-09-08  6:16 ` [PATCH v2 15/32] Safer handling if we can't open /proc/self/uid_map David Gibson
2022-09-08  6:16 ` [PATCH v2 16/32] Move self-isolation code into a separate file David Gibson
2022-09-08  6:16 ` [PATCH v2 17/32] Consolidate validation of pasta namespace options David Gibson
2022-09-08  6:16 ` [PATCH v2 18/32] Clean up and rename conf_ns_open() David Gibson
2022-09-08  6:16 ` [PATCH v2 19/32] Correctly handle --netns-only in pasta_start_ns() David Gibson
2022-09-08  6:16 ` David Gibson [this message]
2022-09-08  6:16 ` [PATCH v2 21/32] Allow --userns when pasta spawns a command David Gibson
2022-09-08  6:16 ` [PATCH v2 22/32] test: Group tests by context then protocol, rather than the reverse David Gibson
2022-09-08  6:16 ` [PATCH v2 23/32] test: Remove unused variable FFMPEG_PID_FILE David Gibson
2022-09-08  6:16 ` [PATCH v2 24/32] test: Actually run cleanup function David Gibson
2022-09-08  6:16 ` [PATCH v2 25/32] test: Create common state directories for temporary files David Gibson
2022-09-08  6:16 ` [PATCH v2 26/32] test: Move context temporary files to state dir David Gibson
2022-09-08  6:16 ` [PATCH v2 27/32] test: Dont regnerate small test file in pasta/tcp David Gibson
2022-09-08  6:16 ` [PATCH v2 28/32] test: Use paths in __STATEDIR__ instead of 'temp' and 'tempdir' directives David Gibson
2022-09-08  6:16 ` [PATCH v2 29/32] test: Move pause temporary file to state directory David Gibson
2022-09-08  6:16 ` [PATCH v2 30/32] test: Store pcap files in $LOGDIR instead of /tmp David Gibson
2022-09-08  6:16 ` [PATCH v2 31/32] test: Move pidfiles and nsholder sockets into state directory David Gibson
2022-09-08  6:16 ` [PATCH v2 32/32] test: Move video processing files to $STATEBASE David Gibson

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20220908061621.2430844-21-david@gibson.dropbear.id.au \
    --to=david@gibson.dropbear.id.au \
    --cc=passt-dev@passt.top \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://passt.top/passt

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for IMAP folder(s).