From: Richard Lawrence <rlawrence@tamu.edu>
To: passt-dev@passt.top
Cc: jbash@jbash.com, Richard Lawrence <rarensu@tamu.edu>,
Richard Lawrence <rlawrence@tamu.edu>
Subject: [PATCH v3] feat: Add cli option '--pass-fds' for pasta mode.
Date: Sun, 19 Jul 2026 15:03:00 -0500 [thread overview]
Message-ID: <20260719200300.201469-1-rlawrence@tamu.edu> (raw)
From: Richard Lawrence <rarensu@tamu.edu>
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.
The `pass-fds` option accepts a comma-separated list of file descriptor numbers. `conf_pass_fds()` parses the command line argument, then `isolate_fds()` skips closing the specified fds by calling `close_range()` on the gaps between them.
Additionally, the tap fd is safely relocated to the lowest unused fd number which it at least 3, to avoid accidentally overwriting an existing fd.
Signed-off-by: Richard Lawrence <rlawrence@tamu.edu>
---
conf.c | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
conf.h | 3 +++
isolation.c | 73 ++++++++++++++++++++++++++++++++++++++++++----------
passt.1 | 5 ++++
4 files changed, 140 insertions(+), 15 deletions(-)
diff --git a/conf.c b/conf.c
index 0fcba5c..729816d 100644
--- a/conf.c
+++ b/conf.c
@@ -732,7 +732,9 @@ pasta_opts:
" Don't copy all addresses to namespace\n"
" --ns-mac-addr ADDR Set MAC address on tap interface\n"
" --no-splice Disable inbound socket splicing\n"
- " --splice-only Only enable loopback forwarding\n");
+ " --splice-only Only enable loopback forwarding\n"
+ " --pass-fds FDS Comma-separated list of fds to pass to\n"
+ " the spawned command\n");
passt_exit(status);
}
@@ -1183,6 +1185,71 @@ int conf_tap_fd(int argc, char **argv)
return val;
}
+/**
+ * conf_pass_fds() - Read fds as supplied by --pass-fds command line option
+ * @argc: Argument count
+ * @argv: Command line options
+ * @fds: Array where we store the parsed fds
+ * @max_fds: Maximum size of the array
+ *
+ * Return: number of parsed fds, or -1 if option not specified
+ */
+int conf_pass_fds(int argc, char **argv, int *fds, int max_fds)
+{
+ const struct option opt[] = { { "pass-fds", required_argument, NULL, 33 },
+ { 0 }, };
+ const char *fdsarg = NULL;
+ int name, fds_cnt = 0;
+ int old_opterr;
+
+ old_opterr = opterr;
+ opterr = 0;
+ optind = 0;
+ do {
+ name = getopt_long(argc, argv, "-:", opt, NULL);
+ if (name == 33)
+ fdsarg = optarg;
+ } while (name != -1);
+ opterr = old_opterr;
+
+ if (!fdsarg)
+ return -1;
+
+ const char *orig_fdsarg = fdsarg;
+
+ while (*fdsarg) {
+ unsigned long val;
+ char *endptr;
+
+ val = strtoul(fdsarg, &endptr, 10);
+ if (fdsarg == endptr) {
+ die("Invalid character in --pass-fds option '%s' (near '%s')",
+ orig_fdsarg, fdsarg);
+ }
+
+ if (val > INT_MAX) {
+ die("Invalid file descriptor in --pass-fds option '%s' (near '%s')",
+ orig_fdsarg, fdsarg);
+ }
+
+ if (fds_cnt >= max_fds)
+ die("Too many file descriptors in --pass-fds");
+
+ fds[fds_cnt++] = (int)val;
+
+ if (*endptr == ',')
+ fdsarg = endptr + 1;
+ else if (*endptr == '\0')
+ fdsarg = endptr;
+ else {
+ die("Invalid character in --pass-fds option '%s' (near '%s')",
+ orig_fdsarg, endptr);
+ }
+ }
+
+ return fds_cnt;
+}
+
/**
* conf_addr() - Configure guest address with -a option
* @c: Execution context
@@ -1331,6 +1398,7 @@ void conf(struct ctx *c, int argc, char **argv)
{"stats", required_argument, NULL, 31 },
{"conf-path", required_argument, NULL, 'c' },
{"chroot-fallback", no_argument, NULL, 32 },
+ {"pass-fds", required_argument, NULL, 33 },
{ 0 },
};
const char *optstring = "+dqfel:hs:c:F:I:p:P:m:a:n:M:g:i:o:D:S:H:461t:u:T:U:";
@@ -1572,6 +1640,10 @@ void conf(struct ctx *c, int argc, char **argv)
case 32:
c->chroot_fallback = true;
break;
+ case 33:
+ if (c->mode != MODE_PASTA)
+ die("--pass-fds is for pasta mode only");
+ break;
case 'd':
c->debug = 1;
c->quiet = 0;
diff --git a/conf.h b/conf.h
index 19bf9bc..28a9acc 100644
--- a/conf.h
+++ b/conf.h
@@ -6,7 +6,10 @@
#ifndef CONF_H
#define CONF_H
+#define PASS_FDS_MAX 1024
+
enum passt_modes conf_mode(int argc, char *argv[]);
+int conf_pass_fds(int argc, char **argv, int *fds, int max_fds);
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);
diff --git a/isolation.c b/isolation.c
index 94cbe7f..25f599b 100644
--- a/isolation.c
+++ b/isolation.c
@@ -249,32 +249,77 @@ void isolate_initial(void)
}
/*
- * isolate_fds() - Close leaked files, but not --fd, stdin, stdout, stderr
+ * isolate_fds() - Close leaked files, but not --fd, --pass-fds, standard streams
* @argc: Argument count
- * @argv: Command line options, as we need to skip any file given via --fd
+ * @argv: Command line options
*
* Should:
- * - close all open files except for standard streams and the one from --fd
+ * - close all open files except for standard streams, --fd, and --pass-fds
* - move the --fd descriptor out of the range 0-2
*
* Return: new fd number for descriptor from --fd, or -1 if not specified
*/
int isolate_fds(int argc, char **argv)
{
- int fd, close_from = STDERR_FILENO + 1;
+ int fds[PASS_FDS_MAX + 1];
+ int fds_cnt = 0;
+ int prev_fd;
+ int next_fd;
+ int tap_fd;
+ int rc = 0;
+ int i;
+
+ tap_fd = conf_tap_fd(argc, argv);
+ if (tap_fd >= 0 && tap_fd < 3) {
+ /* Move the tap fd to a safer location */
+ int new_fd = fcntl(tap_fd, F_DUPFD, STDERR_FILENO + 1);
+
+ if (new_fd < 0)
+ die_perror("Could not relocate --fd descriptor");
+
+ close(tap_fd);
+ tap_fd = new_fd;
+ }
+
+ if (tap_fd >= 0)
+ /* Keep the tap fd */
+ fds[fds_cnt++] = tap_fd;
+
+ rc = conf_pass_fds(argc, argv, fds + fds_cnt, PASS_FDS_MAX);
+ if (rc > 0)
+ /* Keep the pass-fds */
+ fds_cnt += rc;
- fd = conf_tap_fd(argc, argv);
+ rc = 0;
- 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++;
+ /* Keep standard streams */
+ prev_fd = STDERR_FILENO;
+
+ while (1) {
+ next_fd = -1;
+
+ /* Find the next-lowest fd to keep */
+ for (i = 0; i < fds_cnt; i++) {
+ if (fds[i] > prev_fd && (next_fd == -1 || fds[i] < next_fd))
+ next_fd = fds[i];
+ }
+
+ if (next_fd == -1)
+ break;
+
+ if (next_fd > prev_fd + 1) {
+ /* Close fds between two kept fds */
+ if (close_range(prev_fd + 1, next_fd - 1, CLOSE_RANGE_UNSHARE))
+ rc = -1;
+ }
+ prev_fd = next_fd;
}
+
+ /* Close all other fds */
+ if (close_range(prev_fd + 1, ~0U, CLOSE_RANGE_UNSHARE))
+ rc = -1;
- if (close_range(close_from, ~0U, CLOSE_RANGE_UNSHARE)) {
+ if (rc) {
if (errno == ENOSYS || errno == EINVAL) {
/* This probably means close_range() or the
* CLOSE_RANGE_UNSHARE flag is not supported by the
@@ -288,7 +333,7 @@ int isolate_fds(int argc, char **argv)
}
}
- return fd;
+ return tap_fd;
}
/**
diff --git a/passt.1 b/passt.1
index 995590a..bcd3aff 100644
--- a/passt.1
+++ b/passt.1
@@ -755,6 +755,11 @@ of local traffic in pasta\fR in the \fBNOTES\fR for more details.
Do not create a tap device in the namespace. In this mode, \fIpasta\fR only
forwards loopback traffic between namespaces.
+.TP
+.BR \-\-pass\-fds " " \fIfds\fR
+Pass a comma-separated list of file descriptors to the spawned command.
+These file descriptors will be kept open.
+
.SH EXAMPLES
.SS \fBpasta
--
2.52.0
next reply other threads:[~2026-07-19 20:03 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-19 20:03 Richard Lawrence [this message]
2026-07-20 3:25 ` David Gibson
2026-07-20 17:44 ` Stefano Brivio
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=20260719200300.201469-1-rlawrence@tamu.edu \
--to=rlawrence@tamu.edu \
--cc=jbash@jbash.com \
--cc=passt-dev@passt.top \
--cc=rarensu@tamu.edu \
/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).