public inbox for passt-dev@passt.top
 help / color / mirror / code / Atom feed
* [PATCH] feat: Pass open files to child in pasta mode
@ 2026-07-31  6:03 Richard Lawrence
  0 siblings, 0 replies; only message in thread
From: Richard Lawrence @ 2026-07-31  6:03 UTC (permalink / raw)
  To: passt-dev; +Cc: Richard Lawrence

When pasta mode is used to launch an executable (`pasta [COMMAND]`) and
that executable accepts inputs in the form of arbitrary file descriptors
(such as `bwrap`), then passt should not stand in the way of the parent
process handing off those file descriptors to the child process.
See bug 204 for additional discussion.

Changes:
- `conf_tap_fd()` is now called directly by `conf()`.
- The tap fd is relocated to a number at least 3, rather than exactly 3.
- `snapshot_initial_fds()` memorizes inherited fds early in startup.
- Inherited fds are discovered by reading from `/proc` if available.
- `isolate_fds()` is now called after `conf()` in `main()`.

Signed-off-by: Richard Lawrence <rlawrence@tamu.edu>
---
 conf.c      |  39 +++++++++-------
 conf.h      |   1 -
 isolation.c | 128 +++++++++++++++++++++++++++++++++++-----------------
 isolation.h |   9 +++-
 passt.c     |   7 ++-
 5 files changed, 122 insertions(+), 62 deletions(-)

diff --git a/conf.c b/conf.c
index faf2681..6bfdfb6 100644
--- a/conf.c
+++ b/conf.c
@@ -1169,26 +1169,20 @@ static void conf_sock_listen(const struct ctx *c)
 }
 
 /**
- * conf_tap_fd() - Read tap fd as supplied by -F command line option
- * @argc:	Argument count
- * @argv:	Command line options
+ * conf_tap_fd() - Read and relocate tap fd as supplied by -F command line option
+ * @fdarg:	String containing fd
+ *
+ * Should:
+ *  - move the --fd descriptor out of the range 0-2
  *
  * Return: fd number from --fd option, or -1 if not supplied
  */
-int conf_tap_fd(int argc, char **argv)
+int conf_tap_fd(const char *fdarg)
 {
-	const struct option optfd[] = {	{ "fd", required_argument, NULL, 'F' },
-					{ 0 }, };
-	const char *fdarg = NULL, *p;
 	unsigned long val;
-	int name;
-
-	optind = 0;
-	do {
-		name = getopt_long(argc, argv, "-:F:", optfd, NULL);
-		if (name == 'F')
-			fdarg = optarg;
-	} while (name != -1);
+	const char *p;
+	int new_fd;
+	int fd;
 
 	if (!fdarg)
 		return -1;
@@ -1197,7 +1191,18 @@ int conf_tap_fd(int argc, char **argv)
 	if (!parse_unsigned(&p, 0, &val) || !parse_eoi(p) || val > INT_MAX)
 		die("Invalid --fd: %s", fdarg);
 
-	return val;
+	fd = (int)val;
+	if (fd >= 0 && fd < 3) {
+		new_fd = fcntl(fd, F_DUPFD, 3);
+
+		if (new_fd < 0)
+			die_perror("Could not relocate --fd descriptor");
+
+		close(fd);
+		fd = new_fd;
+	}
+
+	return fd;
 }
 
 /**
@@ -1625,7 +1630,7 @@ void conf(struct ctx *c, int argc, char **argv)
 			c->fd_control_listen = c->fd_control = -1;
 			break;
 		case 'F':
-			/* --fd was parsed early and c->fd_tap set in main() */
+			c->fd_tap = conf_tap_fd(optarg);
 			c->one_off = true;
 			*c->sock_path = 0;
 			break;
diff --git a/conf.h b/conf.h
index 19bf9bc..16f9718 100644
--- a/conf.h
+++ b/conf.h
@@ -7,7 +7,6 @@
 #define CONF_H
 
 enum passt_modes conf_mode(int argc, char *argv[]);
-int conf_tap_fd(int argc, char **argv);
 void conf(struct ctx *c, int argc, char **argv);
 void conf_listen_handler(struct ctx *c, uint32_t events);
 void conf_handler(struct ctx *c, uint32_t events);
diff --git a/isolation.c b/isolation.c
index 94cbe7f..bedc4ce 100644
--- a/isolation.c
+++ b/isolation.c
@@ -24,19 +24,13 @@
  * done anything we need to do with those resources, so we have
  * multiple stages of self-isolation.  In order these are:
  *
- * 1a. isolate_initial()
+ * 1. isolate_initial()
  * ====================
  *
  * Executed immediately after startup, drops capabilities we don't
  * need at any point during execution (or which we gain back when we
  * need by joining other namespaces).
  *
- * 1b. isolate_fds()
- * ================
- *
- * Executed immediately after isolate_initial().  Closes any leaked
- * files we might have inherited from the parent process.
- *
  * 2. isolate_user()
  * =================
  *
@@ -44,14 +38,20 @@
  * operate in.  Sets our final UID & GID, and enters the correct user
  * namespace.
  *
- * 3. isolate_prefork()
+ * 3. isolate_fds()
+ * ================
+ *
+ * Executed after conf().  Closes any leaked
+ * files we might have inherited from the parent process.
+ *
+ * 4. isolate_prefork()
  * ====================
  *
  * Executed after all setup, but before daemonising (fork()ing into
  * the background).  Uses mount namespace and pivot_root() to remove
  * our access to the filesystem.
  *
- * 4. isolate_postfork()
+ * 5. isolate_postfork()
  * =====================
  *
  * Executed immediately after daemonizing, but before entering the
@@ -61,6 +61,7 @@
  * runtime operation.
  */
 
+#include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <grp.h>
@@ -77,6 +78,7 @@
 #include <unistd.h>
 #include <sys/mount.h>
 #include <sys/prctl.h>
+#include <sys/resource.h>
 #include <sys/socket.h>
 #include <sys/syscall.h>
 #include <sys/types.h>
@@ -248,47 +250,89 @@ void isolate_initial(void)
 	drop_caps_ep_except(keep);
 }
 
-/*
- * isolate_fds() - Close leaked files, but not --fd, stdin, stdout, stderr
- * @argc:	Argument count
- * @argv:	Command line options, as we need to skip any file given via --fd
- *
- * Should:
- *  - close all open files except for standard streams and the one from --fd
- *  - move the --fd descriptor out of the range 0-2
- *
- * Return: new fd number for descriptor from --fd, or -1 if not specified
+/**
+ * snapshot_initial_fds() - Snapshot initial file descriptors inherited from parent
+ * other than standard streams (stdin, stdout, stderr)
+ * @ifds:	Snapshot struct of initial file descriptors to populate
  */
-int isolate_fds(int argc, char **argv)
+void snapshot_initial_fds(struct initial_fd_snapshot *ifds)
 {
-	int fd, close_from = STDERR_FILENO + 1;
+	struct dirent *entry;
+	size_t capacity = 16;
+	int max_fd = 1024;
+	struct rlimit rl;
+	int dir_fd;
+	DIR *dir;
+	int fd;
+
+	ifds->arr = NULL;
+	ifds->count = 0;
+
+	dir = opendir("/proc/self/fd");
+	if (dir) {
+		dir_fd = dirfd(dir);
+		ifds->arr = malloc(sizeof(int) * capacity);
+		if (!ifds->arr)
+			die_perror("Failed to allocate memory for inherited fds");
+
+		while ((entry = readdir(dir)) != NULL) {
+			if (entry->d_name[0] < '0' || entry->d_name[0] > '9')
+				continue;
+
+			fd = atoi(entry->d_name);
+			/* Ignore stdin/stdout/stderr and the opendir handle itself */
+			if (fd > STDERR_FILENO && fd != dir_fd) {
+				if (ifds->count >= capacity) {
+					capacity *= 2;
+					ifds->arr = realloc(ifds->arr,
+							    sizeof(int) * capacity);
+					if (!ifds->arr)
+						die_perror("Failed to reallocate memory for inherited fds");
+				}
+				ifds->arr[ifds->count++] = fd;
+			}
+		}
+		closedir(dir);
+		return;
+	}
+
+	/* Fallback for environments without /proc (e.g. minimal chroots) */
+	if (getrlimit(RLIMIT_NOFILE, &rl) == 0 && rl.rlim_cur != RLIM_INFINITY)
+		max_fd = (int)rl.rlim_cur;
 
-	fd = conf_tap_fd(argc, argv);
+	ifds->arr = malloc(sizeof(int) * max_fd);
+	if (!ifds->arr)
+		die_perror("Failed to allocate memory for inherited fds fallback");
 
-	if (fd >= 0) {
-		/* Move the passed fd to a more convenient location */
-		if (fd != close_from			&&
-		    (dup2(fd, close_from) != close_from	||
-		     close(fd)))
-			die_perror("Could not move --fd descriptor");
-		fd = close_from++;
+	for (fd = STDERR_FILENO + 1; fd < max_fd; fd++) {
+		if (fcntl(fd, F_GETFD) >= 0)
+			ifds->arr[ifds->count++] = fd;
 	}
+}
 
-	if (close_range(close_from, ~0U, CLOSE_RANGE_UNSHARE)) {
-		if (errno == ENOSYS || errno == EINVAL) {
-			/* This probably means close_range() or the
-			 * CLOSE_RANGE_UNSHARE flag is not supported by the
-			 * kernel.  Not much we can do here except carry on and
-			 * hope for the best.
-			 */
-			warn(
-"Can't use close_range() to ensure no files leaked by parent");
-		} else {
-			die_perror("Failed to close files leaked by parent");
-		}
+/**
+ * isolate_fds() - Close leaked files from the parent process
+ * @ifds:	Snapshot of initial file descriptors
+ * @keep_fd:	File descriptor to keep open, if any
+ *
+ * Should:
+ *  - close all file descriptors that were open at startup, except for keep_fd
+ */
+void isolate_fds(struct initial_fd_snapshot *ifds, int keep_fd)
+{
+	size_t i;
+
+	if (!ifds || !ifds->arr)
+		return;
+
+	for (i = 0; i < ifds->count; i++) {
+		if (ifds->arr[i] != keep_fd)
+			close(ifds->arr[i]);
 	}
 
-	return fd;
+	free(ifds->arr);
+	ifds->arr = NULL;
+	ifds->count = 0;
 }
 
 /**
diff --git a/isolation.h b/isolation.h
index ec47038..8ada988 100644
--- a/isolation.h
+++ b/isolation.h
@@ -8,10 +8,17 @@
 #define ISOLATION_H
 
 #include <stdbool.h>
+#include <stddef.h>
 #include <unistd.h>
 
+struct initial_fd_snapshot {
+	int *arr;
+	size_t count;
+};
+
 void isolate_initial(void);
-int isolate_fds(int argc, char **argv);
+void snapshot_initial_fds(struct initial_fd_snapshot *ifds);
+void isolate_fds(struct initial_fd_snapshot *ifds, int keep_fd);
 void isolate_user(const struct ctx *c, uid_t uid, gid_t gid, bool use_userns,
 		  const char *userns);
 int isolate_prefork(const struct ctx *c);
diff --git a/passt.c b/passt.c
index 5054551..c4335bf 100644
--- a/passt.c
+++ b/passt.c
@@ -335,6 +335,7 @@ int main(int argc, char **argv)
 	struct epoll_event events[NUM_EPOLL_EVENTS];
 	int nfds, devnull_fd = -1, fd;
 	struct ctx *c = &passt_ctx;
+	struct initial_fd_snapshot ifds;
 	struct rlimit limit;
 	struct timespec now;
 	struct sigaction sa;
@@ -344,8 +345,9 @@ int main(int argc, char **argv)
 
 	arch_avx2_exec(argv);
 
+	snapshot_initial_fds(&ifds);
+
 	isolate_initial();
-	c->fd_tap = isolate_fds(argc, argv);
 
 	if ((devnull_fd = open("/dev/null", O_RDWR | O_CLOEXEC)) < 0)
 		die_perror("Failed to open /dev/null");
@@ -390,6 +392,9 @@ int main(int argc, char **argv)
 	sock_probe_features(c);
 
 	conf(c, argc, argv);
+
+	isolate_fds(&ifds, c->fd_tap);
+	
 	trace_init(c->trace);
 
 	pasta_netns_quit_init(c);
-- 
2.52.0


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2026-07-31  6:06 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-07-31  6:03 [PATCH] feat: Pass open files to child in pasta mode Richard Lawrence

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).