From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: passt.top; dmarc=pass (p=quarantine dis=none) header.from=tamu.edu Authentication-Results: passt.top; dkim=pass (2048-bit key; secure) header.d=tamu.edu header.i=@tamu.edu header.a=rsa-sha256 header.s=ppae6d7b header.b=cXeNFocI; dkim-atps=neutral Received: from mx0a-00178102.pphosted.com (mx0a-00178102.pphosted.com [148.163.135.245]) by passt.top (Postfix) with ESMTPS id 22E035A0265 for ; Sun, 19 Jul 2026 22:03:13 +0200 (CEST) Received: from pps.filterd (m0231227.ppops.net [127.0.0.1]) by mx0b-00178102.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id 66JIt4kv3624222; Sun, 19 Jul 2026 15:03:08 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=tamu.edu; h=cc :content-transfer-encoding:date:from:message-id:mime-version :subject:to; s=ppae6d7b; bh=FdAT/ufh2LeK8+9ce9ZuNyNYL4zglatX18iN 3XmJv1U=; b=cXeNFocIhq+tmmamT2mLlHbk+biTG7mER6gqGhaI7diPfbqL9uUX urKu68MymwQV3AEJBvC8poXNBd7BJfWY1JmStKeOnm6j6jRz5Znk8pPxqDT4Mg/U obbsdUiWjTCxW58kMqrYG/Xaab4m8KMHW3AqP8h0biRCCvRGnfUehXBAiJ07MouZ 7kExxzA7erPAwN2EZL4YrYqKcf2lXxbGjSwNMELULV9FcAPJ9fpZtXPBw/dojPAa /bQK2p/LOyn4VQh/co70vGE5rbIMvC7PGi/9L4hpUQCyDVwj2QkohoKt+vjF/foK Bm8K8Det6YOws8HgA3WrvfDiQc61AwJdQA== Received: from bespin.localdomain ([128.194.2.191]) by mx0b-00178102.pphosted.com (PPS) with ESMTPS id 4fg8kn758h-1 (version=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384 bits=256 verify=NOT); Sun, 19 Jul 2026 15:03:08 -0500 (CDT) Received: by bespin.localdomain (Postfix, from userid 1000) id 77FA86841; Sun, 19 Jul 2026 15:03:07 -0500 (CDT) From: Richard Lawrence To: passt-dev@passt.top Subject: [PATCH v3] feat: Add cli option '--pass-fds' for pasta mode. Date: Sun, 19 Jul 2026 15:03:00 -0500 Message-ID: <20260719200300.201469-1-rlawrence@tamu.edu> X-Mailer: git-send-email 2.52.0 MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Proofpoint-Reinject: loops=2 maxloops=12 X-Proofpoint-GUID: OenFAoqXAGvppwmI1pWmdUhDZvqKW_vU X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwNzE5MDIyNCBTYWx0ZWRfXw3yLgFxPrHsg 9NT2bdKgCNC3YSsZw7l5gyhNn3PIu7wXnqdZ2fhUzGkpYMqS+0+PfxA7kkTcsHNSUGNUbQos6/6 cCye4a6cRriMH3djOm/kLodDN1JO/JSg/SMEJTsbj6VHupRknl6NNu5CcL7aKQ1biLR2Et//qou UNsaKVbw3MRdhx2OY8wO0/hNWHCCXPDUYqaqF2OThe5CiuWSOps9Wr+rUHtV0Htq2k3aChKWka2 4C66nbNne+S6i4EqwZpY3rW1zf6JkgztdEj79JQD6MAlUaOgIrGbV6jlASCuz9gcZ29f1Nf+EWZ jFrRnJQRuMUvrWgq425yGq5v+JrGChl6flGYfmZ+FxI+iOGvdHcvJiKp41RggUmB61PmvG7AT7H gH0E8cpThzwby0qRLRG0QpZXjCzybRjkwjT5wB1mZp08zDMzdDYllJliMDsVJJqHMm1BPTv0PMP LsjyHzHEJstshowr3ng== X-Proofpoint-ORIG-GUID: 7_Qz4KZKuezeTsJGb30wmUhUFxCFKcRz X-Authority-Analysis: v=2.4 cv=V6lNF+ni c=1 sm=1 tr=0 ts=6a5d2d7c cx=c_pps a=RM/V1YFJ8wA4X+FDJsk8mQ==:117 a=RM/V1YFJ8wA4X+FDJsk8mQ==:17 a=RAioF0-LDSMA:10 a=x7bEGLp0ZPQA:10 a=VkNPw1HP01LnGYTKEx00:22 a=HuL5yKgYSaDc2Nh3iM7q:22 a=efzhd9O6aJ8jRLlRsDx9:22 a=61hw28DCpeuav6MdgB0A:9 X-Proofpoint-Spam-Info: AW1haW4tMjYwNzE5MDIyNCBTYWx0ZWRfXxCeSF+PKRC3W 3fNqVxmR4fj29SWMleF2f0TDsp8OE0Ss1G9td3yqyeWPwwmcOJI5v3g8ToBTa8cl22FzmtitD3Z A0gbE05juCNMQV4ZxyFkwS7Syc5y3sw= X-MailFrom: rlawrence@bespin.localdomain X-Mailman-Rule-Hits: nonmember-moderation X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; emergency; loop; banned-address; member-moderation Message-ID-Hash: ZUBAJLM33NWETD3JWX6QG6QEWG235OKN X-Message-ID-Hash: ZUBAJLM33NWETD3JWX6QG6QEWG235OKN X-Mailman-Approved-At: Sun, 19 Jul 2026 22:35:56 +0200 CC: jbash@jbash.com, Richard Lawrence , Richard Lawrence X-Mailman-Version: 3.3.8 Precedence: list List-Id: Development discussion and patches for passt Archived-At: Archived-At: List-Archive: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: 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. 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 --- 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