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=hmv17GNi; dkim-atps=neutral Received: from mx0a-00178102.pphosted.com (mx0a-00178102.pphosted.com [148.163.135.245]) by passt.top (Postfix) with ESMTPS id 09A955A0262 for ; Sat, 18 Jul 2026 18:14:41 +0200 (CEST) Received: from pps.filterd (m0169866.ppops.net [127.0.0.1]) by mx0a-00178102.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id 66IG0Bsl802692; Sat, 18 Jul 2026 11:14:37 -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=QbzZQHVxm5fm+4NYvANKrZkkO/jsMC1fjQQa kcK0TiE=; b=hmv17GNi3uIyaK3PZXCwQZyyZi6Hr3bxS2W0BKQB//j5h1JfFDuL GR4q2m/6wpOCtgF3DUoFOxhzeC2l8qgc79G5Ry8ooM5kul8//Wm5pTFzaOixImfL u273c9Gf+Oy/yFsMrcTy6Q8JzgVrsXaBEaQTWe3na7qDcI6KrvdCukNUpt3KSaFX WccfnCy/aqQz0yoht9OJZu2st6MHNv3JJo+33IYaBceAwPwXR5ONlQb34tnPjymR bWU81x4b3VBMF1SOjKTBnicHFZ4WuH6BAevNjuZLhFFbcQC/MGcXYduAI0P5xGAy xvNWgvyHdqbWo1rh6dd2tUeaSw2n1HZlUw== Received: from bespin.localdomain ([128.194.2.191]) by mx0a-00178102.pphosted.com (PPS) with ESMTPS id 4fg8hnsfpv-1 (version=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384 bits=256 verify=NOT); Sat, 18 Jul 2026 11:14:36 -0500 (CDT) Received: by bespin.localdomain (Postfix, from userid 1000) id F3DFF6844; Sat, 18 Jul 2026 11:14:35 -0500 (CDT) From: Richard Lawrence To: passt-dev@passt.top Subject: [PATCH v2] feat: Add cli option '--pass-fds' for pasta mode. Date: Sat, 18 Jul 2026 11:14:29 -0500 Message-ID: <20260718161429.173494-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-Spam-Info: AW1haW4tMjYwNzE4MDE2OSBTYWx0ZWRfX7iIeXVc2cvRu U93ljuqDy9e55C6kZGWdsp5D5+bw6Pl6usqwZ7N/8zQpcHZ4C/20cPteO6fwughBMMkbFd3UtDX oKGXALkWwGvQQAl0a5IuaHqwJdrq7iI= X-Proofpoint-GUID: OoxZqN-U_Z8CSlCJhR4uxDrhILBltYZ9 X-Authority-Analysis: v=2.4 cv=cMrQdFeN c=1 sm=1 tr=0 ts=6a5ba66c 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=PpoWY9mOLvn6BEvM-Ecr:22 a=61hw28DCpeuav6MdgB0A:9 X-Proofpoint-ORIG-GUID: mEMVyNi2FZbIFFVmBVeU2rlXuyT1Oagu X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwNzE4MDE2OSBTYWx0ZWRfX/QfAxDXR+YW0 g5ZhAg1sqvKQunlUItwghgnC5MXSon67IH2qigD6zorLhxlwH4jxEIBv4OmlCD0zjjVuqIaQXTG 02+oiNiXOosk3irMD1kkO7AwMENznJgWw1YNRZfmuFaCPnwhYuszqPBRswfwfY8VGT+URkiRSrt RNMUNyaRkein8qs0Xm6TNLjD/n26U8Bkh07Y23adDfOlHpDD1ctxwI58H1JODzQJcD5ABULyPQu LFHmsiKyot9Fq05JnkhJpkPlJH4pcro0MjadIY6RoF2SiAn/4LMseWAlpkeiGbsyS2FOhsjNTBM i6eZE5zI7MN/cyMv8K1sNy6gDnQABew+rnjGA68jsiGPFPUFTmZ5FILQGwy0PiqVZNi7Nl6MqKJ B0myw35BrVTh9Cfgefad/5A9ZrBMtKIs/x6BUJksrFM69jB/+VjixE+GIE2oaWnY4P0IqMOHmoI 5Hkz2DBp1SGutiQw2BQ== 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: C2QTABL3KAEP2WLXJ6JFTKKHZX7U7IHZ X-Message-ID-Hash: C2QTABL3KAEP2WLXJ6JFTKKHZX7U7IHZ X-Mailman-Approved-At: Sun, 19 Jul 2026 17:03:40 +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 | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++- conf.h | 1 + isolation.c | 68 ++++++++++++++++++++++++++++++++++++++++++----------- passt.1 | 5 ++++ 4 files changed, 125 insertions(+), 15 deletions(-) diff --git a/conf.c b/conf.c index 0fcba5c..3246735 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,63 @@ 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; + + while (*fdsarg) { + unsigned long val; + char *endptr; + + val = strtoul(fdsarg, &endptr, 10); + if (fdsarg == endptr) + die("Invalid --pass-fds option: %s", fdsarg); + + if (val > INT_MAX) + die("Invalid file descriptor in --pass-fds: %lu", val); + + 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", fdsarg); + } + + return fds_cnt; +} + /** * conf_addr() - Configure guest address with -a option * @c: Execution context @@ -1331,6 +1390,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 +1632,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..3489f35 100644 --- a/conf.h +++ b/conf.h @@ -7,6 +7,7 @@ #define CONF_H 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..0632f7f 100644 --- a/isolation.c +++ b/isolation.c @@ -249,32 +249,72 @@ 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 prev_fd = STDERR_FILENO; // Keep standard streams + int fds[1024 + 1]; + int fds_cnt = 0; + int tap_fd; + int rc = 0; + + tap_fd = conf_tap_fd(argc, argv); + if (tap_fd >= 0 && tap_fd < 3) { + /* Move the passed fd to a more convenient location */ + int new_fd = fcntl(tap_fd, F_DUPFD, 3); + + if (new_fd < 0) + die_perror("Could not relocate --fd descriptor"); - fd = conf_tap_fd(argc, argv); + close(tap_fd); + tap_fd = new_fd; + } - 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++; + if (tap_fd >= 0) + /* Keep the tap fd */ + fds[fds_cnt++] = tap_fd; + + rc = conf_pass_fds(argc, argv, fds + fds_cnt, 1024); + if (rc > 0) + /* Keep the pass-fds */ + fds_cnt += rc; + + rc = 0; + + while (1) { + int min_fd = -1; + + /* Find the next-lowest fd to keep */ + for (int i = 0; i < fds_cnt; i++) { + if (fds[i] > prev_fd && (min_fd == -1 || fds[i] < min_fd)) + min_fd = fds[i]; + } + + if (min_fd == -1) + break; + + if (min_fd > prev_fd + 1) { + /* Close fds between two kept fds */ + if (close_range(prev_fd + 1, min_fd - 1, CLOSE_RANGE_UNSHARE)) + rc = -1; + } + prev_fd = min_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 +328,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