From d11c14a9817f6561a30d96d8faea126a4c811af8 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 4 Nov 2022 18:19:29 +0100 Subject: [PATCH 1/4] fd-util: make fd_in_set() (and thus close_all_fds()) handle invalidated fds in the array let's handle gracefully if fds in the specified array are already invalidated (i.e. negative). This is handy when putting together arrays on the fly. --- src/basic/fd-util.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index cee20a9a81..6ed04449bf 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -177,9 +177,13 @@ int fd_cloexec(int fd, bool cloexec) { _pure_ static bool fd_in_set(int fd, const int fdset[], size_t n_fdset) { assert(n_fdset == 0 || fdset); - for (size_t i = 0; i < n_fdset; i++) + for (size_t i = 0; i < n_fdset; i++) { + if (fdset[i] < 0) + continue; + if (fdset[i] == fd) return true; + } return false; } @@ -252,6 +256,10 @@ static int close_all_fds_special_case(const int except[], size_t n_except) { if (!have_close_range) return 0; + if (n_except == 1 && except[0] < 0) /* Minor optimization: if we only got one fd, and it's invalid, + * we got none */ + n_except = 0; + switch (n_except) { case 0: From ed18c22c989495aab36512f03449222cfcf79aa7 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 4 Nov 2022 18:20:19 +0100 Subject: [PATCH 2/4] fd-util: add new fd_cloexec_many() helper --- src/basic/fd-util.c | 19 +++++++++++++++++++ src/basic/fd-util.h | 1 + 2 files changed, 20 insertions(+) diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index 6ed04449bf..66bb7569bb 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -174,6 +174,25 @@ int fd_cloexec(int fd, bool cloexec) { return RET_NERRNO(fcntl(fd, F_SETFD, nflags)); } +int fd_cloexec_many(const int fds[], size_t n_fds, bool cloexec) { + int ret = 0, r; + + assert(n_fds == 0 || fds); + + for (size_t i = 0; i < n_fds; i++) { + if (fds[i] < 0) /* Skip gracefully over already invalidated fds */ + continue; + + r = fd_cloexec(fds[i], cloexec); + if (r < 0 && ret >= 0) /* Continue going, but return first error */ + ret = r; + else + ret = 1; /* report if we did anything */ + } + + return ret; +} + _pure_ static bool fd_in_set(int fd, const int fdset[], size_t n_fdset) { assert(n_fdset == 0 || fdset); diff --git a/src/basic/fd-util.h b/src/basic/fd-util.h index d9896e27e8..29c7d86f27 100644 --- a/src/basic/fd-util.h +++ b/src/basic/fd-util.h @@ -56,6 +56,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(DIR*, closedir, NULL); int fd_nonblock(int fd, bool nonblock); int fd_cloexec(int fd, bool cloexec); +int fd_cloexec_many(const int fds[], size_t n_fds, bool cloexec); int get_max_fd(void); From 981cfbe046297a18f2cb115ef81202f3bd68d2a3 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 4 Nov 2022 18:20:47 +0100 Subject: [PATCH 3/4] process-util: add new FORK_CLOEXEC_OFF flag for disabling O_CLOEXEC on remaining fds Often the fds that shall stay around in the child shall be passed to a process over execve(), hence add an option to explicitly disable O_CLOEXEC on them in the child. --- src/basic/process-util.c | 8 ++++++++ src/basic/process-util.h | 1 + 2 files changed, 9 insertions(+) diff --git a/src/basic/process-util.c b/src/basic/process-util.c index fb0b38fa49..0213f5913f 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -1372,6 +1372,14 @@ int safe_fork_full( } } + if (flags & FORK_CLOEXEC_OFF) { + r = fd_cloexec_many(except_fds, n_except_fds, false); + if (r < 0) { + log_full_errno(prio, r, "Failed to turn off O_CLOEXEC on file descriptors: %m"); + _exit(EXIT_FAILURE); + } + } + /* When we were asked to reopen the logs, do so again now */ if (flags & FORK_REOPEN_LOG) { log_open(); diff --git a/src/basic/process-util.h b/src/basic/process-util.h index f8c374a310..ed2f73673e 100644 --- a/src/basic/process-util.h +++ b/src/basic/process-util.h @@ -150,6 +150,7 @@ typedef enum ForkFlags { FORK_STDOUT_TO_STDERR = 1 << 11, /* Make stdout a copy of stderr */ FORK_FLUSH_STDIO = 1 << 12, /* fflush() stdout (and stderr) before forking */ FORK_NEW_USERNS = 1 << 13, /* Run child in its own user namespace */ + FORK_CLOEXEC_OFF = 1 << 14, /* In the child: turn off O_CLOEXEC on all fds in except_fds[] */ } ForkFlags; int safe_fork_full(const char *name, const int except_fds[], size_t n_except_fds, ForkFlags flags, pid_t *ret_pid); From f8ab781223bcb0330ee4499b879a62e84fee313e Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 4 Nov 2022 18:26:42 +0100 Subject: [PATCH 4/4] dissect: fix fsck Since f7725647bb41c3398a867f139efe526efe8aa1b3 when dissecting a disk image we operate with fds to the device nodes in question wherever we can. This includes when we fork off fsck, where we pass a /proc/self/fd/ path as argument. This only works if we keep that fd open however and disable O_CLOEXEC on the fd. Hence do so, and fix fsck this way. (Without this, all fsck will fail, since the fd path is invalid) --- src/shared/dissect-image.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 2989d31d3c..cfa7d7e0f3 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -1327,11 +1327,11 @@ static int is_loop_device(const char *path) { return true; } -static int run_fsck(const char *node, const char *fstype) { +static int run_fsck(int node_fd, const char *fstype) { int r, exit_status; pid_t pid; - assert(node); + assert(node_fd >= 0); assert(fstype); r = fsck_exists_for_fstype(fstype); @@ -1340,16 +1340,20 @@ static int run_fsck(const char *node, const char *fstype) { return 0; } if (r == 0) { - log_debug("Not checking partition %s, as fsck for %s does not exist.", node, fstype); + log_debug("Not checking partition %s, as fsck for %s does not exist.", FORMAT_PROC_FD_PATH(node_fd), fstype); return 0; } - r = safe_fork("(fsck)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_NULL_STDIO, &pid); + r = safe_fork_full( + "(fsck)", + &node_fd, 1, /* Leave the node fd open */ + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_NULL_STDIO|FORK_CLOEXEC_OFF, + &pid); if (r < 0) return log_debug_errno(r, "Failed to fork off fsck: %m"); if (r == 0) { /* Child */ - execl("/sbin/fsck", "/sbin/fsck", "-aT", node, NULL); + execl("/sbin/fsck", "/sbin/fsck", "-aT", FORMAT_PROC_FD_PATH(node_fd), NULL); log_open(); log_debug_errno(errno, "Failed to execl() fsck: %m"); _exit(FSCK_OPERATIONAL_ERROR); @@ -1439,7 +1443,7 @@ static int mount_partition( rw = m->rw && !(flags & DISSECT_IMAGE_MOUNT_READ_ONLY); if (FLAGS_SET(flags, DISSECT_IMAGE_FSCK) && rw) { - r = run_fsck(node, fstype); + r = run_fsck(m->mount_node_fd, fstype); if (r < 0) return r; }