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=epbWmjkf; dkim-atps=neutral Received: from mx0a-00178102.pphosted.com (mx0a-00178102.pphosted.com [148.163.135.245]) by passt.top (Postfix) with ESMTPS id 1CF1D5A0265 for ; Fri, 31 Jul 2026 08:06:05 +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 66V5fpuV4133584 for ; Fri, 31 Jul 2026 01:06:04 -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=CAxQhu0G1lVn5pbbWcBu7DCAHyySP25exGwn rhFV3Bk=; b=epbWmjkfaT3qKnMM6C6GAsyJnvnFgelRHAoP7M/Pz904QfMydo88 lcVfjUKqsyqw2hdLy33Aedm6tJi7lxk7WrcGRgJZEOPOdaw9MmwLNjM/sWitihPS fqYSjGR01TsBQ6SCgFmwOX2XPfc5mXrlM9e4YSLzHmMn85xjNyWX7FpjDQscAE3y U0VoHVVntLb5QL6q52TGukm3Z79CMl6dgHJiMNGbvzNDSFaX7S8zrpx4S08dAAex ihK/ZtFTIT+XdZuoaTpSw1+9EGY9ncL2h4f1MsuZ9dkO5lf4ebNHDmlvjfn10NEB j7yp3h7tX+d6lhbiiicln/+e5KkTfIPOGg== Received: from bespin.localdomain ([128.194.2.191]) by mx0b-00178102.pphosted.com (PPS) with ESMTPS id 4frjva0kf2-1 (version=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384 bits=256 verify=NOT); Fri, 31 Jul 2026 01:06:03 -0500 (CDT) Received: by bespin.localdomain (Postfix, from userid 1000) id 2107D201; Fri, 31 Jul 2026 01:06:03 -0500 (CDT) From: Richard Lawrence To: passt-dev@passt.top Subject: [PATCH] feat: Pass open files to child in pasta mode Date: Fri, 31 Jul 2026 01:03:41 -0500 Message-ID: <20260731060341.302560-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: OLnbFeIWMldKVsZHIXXD5IHsZngcnynT X-Authority-Analysis: v=2.4 cv=d+rFDxjE c=1 sm=1 tr=0 ts=6a6c3b4c 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=bucnvzA0mEatisX14UYA:9 a=O8hF6Hzn-FEA:10 X-Proofpoint-Spam-Details-Enc: AW1haW4tMjYwNzMxMDAzOSBTYWx0ZWRfXxE2bUH955jDb KUNpWF2fvHrre+2/VCrxUT3RMceg5KiXkvBxtOYPx7441tn0wZMIxFlqPPtVSbXqtN9gB38Rm3k Dv/ZezSRCT9PuV9LJGTF+1kz6FXVkHuyLyZ2gJ0xqMfX4Nw5c7/hE+CFdFsygbhQm5YkbjbWusP FuRQdHiLLFGNXEP1Y6ra/O2Xe2sQvI7Va85928LkEOgS9M9qrvC+9kWToj+SbezbeUX3t1wt2+q qt+/OXa/J11C7dm7Hb2cDpZZOdQlnj7EWCEY3k7KrWmKSYEe1OlmRq235VpJj4xd6EMpCVIeMfF Vvwxsi8YeFoxpYfOCY7PZlHtCNUfmASc0/L6q5gmbCpBp+ey9YJwcxqVWBRnS31SBbdkJEW1nKq UdB6Aog9Fzk8nul1P9d5CDHp1zpyp+DCXbSzg2MvlOf10YQGPVECdbXThqMFnXyVv5iUE9h5KNr wNCNm6URo+gU/C52hAw== X-Proofpoint-Spam-Info: AW1haW4tMjYwNzMxMDAzOSBTYWx0ZWRfXymVQbLTGq+Vz alQjE5CiYl58xNADxnMX1crpPe/hOHBdvhmLmTu/6RJVPuirm0t+x1rNi5VlfrzfVCRUTzfdx83 kEYb05wwcXgbv6lHWNj0B5KdPCvRspA= X-Proofpoint-ORIG-GUID: 5hVqwTcn18sNViZmCDiGOPdMIPAYYf9O 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: D4C5P627QQVN74YFCJG4GX44BJU25PGV X-Message-ID-Hash: D4C5P627QQVN74YFCJG4GX44BJU25PGV X-Mailman-Approved-At: Fri, 31 Jul 2026 09:12:53 +0200 CC: 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: 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 --- 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 #include #include #include @@ -77,6 +78,7 @@ #include #include #include +#include #include #include #include @@ -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 +#include #include +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