From: Li Chen <me@linux.beauty>
To: Christian Brauner <brauner@kernel.org>,
Kees Cook <kees@kernel.org>,
Alexander Viro <viro@zeniv.linux.org.uk>
Cc: linux-fsdevel@vger.kernel.org, linux-api@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-mm@kvack.org,
linux-arch@vger.kernel.org, linux-doc@vger.kernel.org,
linux-kselftest@vger.kernel.org, x86@kernel.org,
Arnd Bergmann <arnd@arndb.de>, Andy Lutomirski <luto@kernel.org>,
Thomas Gleixner <tglx@kernel.org>, Ingo Molnar <mingo@redhat.com>,
Borislav Petkov <bp@alien8.de>,
Dave Hansen <dave.hansen@linux.intel.com>,
"H. Peter Anvin" <hpa@zytor.com>, Jan Kara <jack@suse.cz>,
Jonathan Corbet <corbet@lwn.net>,
Shuah Khan <skhan@linuxfoundation.org>, Li Chen <me@linux.beauty>
Subject: [RFC PATCH v1 13/13] selftests/exec: cover spawn template basics
Date: Thu, 28 May 2026 17:52:34 +0800 [thread overview]
Message-ID: <20260528095235.2491226-14-me@linux.beauty> (raw)
In-Reply-To: <20260528095235.2491226-1-me@linux.beauty>
Add exec selftests for the spawn_template ABI. Cover basic spawning,
relative path rejection, execfd execute-permission checks, default fd
closing, close-range actions using newfd -1, and stale path rejection
after executable metadata changes.
Also cover atomic path replacement while a template fd for an old path is
still alive. The old template must reject the changed path with ESTALE, and
a new template for the same path must execute the replacement.
Signed-off-by: Li Chen <me@linux.beauty>
---
MAINTAINERS | 1 +
tools/testing/selftests/exec/Makefile | 1 +
tools/testing/selftests/exec/spawn_template.c | 997 ++++++++++++++++++
3 files changed, 999 insertions(+)
create mode 100644 tools/testing/selftests/exec/spawn_template.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 3e737097940f9..77b3da32b4d2a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9747,6 +9747,7 @@ F: include/uapi/linux/spawn_template.h
F: kernel/fork.c
F: mm/vma_exec.c
F: tools/testing/selftests/exec/
+F: tools/testing/selftests/exec/spawn_template.c
N: asm/elf.h
N: binfmt
diff --git a/tools/testing/selftests/exec/Makefile b/tools/testing/selftests/exec/Makefile
index 45a3cfc435cfd..cf39fe916b9ba 100644
--- a/tools/testing/selftests/exec/Makefile
+++ b/tools/testing/selftests/exec/Makefile
@@ -20,6 +20,7 @@ TEST_FILES := Makefile
TEST_GEN_PROGS += recursion-depth
TEST_GEN_PROGS += null-argv
TEST_GEN_PROGS += check-exec
+TEST_GEN_PROGS += spawn_template
EXTRA_CLEAN := $(OUTPUT)/subdir.moved $(OUTPUT)/execveat.moved $(OUTPUT)/xxxxx* \
$(OUTPUT)/S_I*.test
diff --git a/tools/testing/selftests/exec/spawn_template.c b/tools/testing/selftests/exec/spawn_template.c
new file mode 100644
index 0000000000000..26708143ac9dc
--- /dev/null
+++ b/tools/testing/selftests/exec/spawn_template.c
@@ -0,0 +1,997 @@
+// SPDX-License-Identifier: GPL-2.0
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <linux/spawn_template.h>
+
+#include "kselftest.h"
+
+#ifndef __NR_spawn_template_create
+#define __NR_spawn_template_create 472
+#endif
+
+#ifndef __NR_spawn_template_spawn
+#define __NR_spawn_template_spawn 473
+#endif
+
+#define SPAWN_TEMPLATE_MISSING_SYSCALL_ERRNO 38
+#define SPAWN_TEMPLATE_KERNEL_NSIG 64
+#define SPAWN_TEMPLATE_KERNEL_SIGSET_WORDS \
+ (SPAWN_TEMPLATE_KERNEL_NSIG / (8 * sizeof(unsigned long)))
+
+static const char *true_path;
+static char self_path[PATH_MAX];
+
+struct spawn_template_kernel_sigset {
+ unsigned long sig[SPAWN_TEMPLATE_KERNEL_SIGSET_WORDS];
+};
+
+static void spawn_template_kernel_sigempty(struct spawn_template_kernel_sigset *set)
+{
+ memset(set, 0, sizeof(*set));
+}
+
+static void spawn_template_kernel_sigadd(struct spawn_template_kernel_sigset *set,
+ int sig)
+{
+ sig--;
+ set->sig[sig / (8 * sizeof(unsigned long))] |=
+ 1UL << (sig % (8 * sizeof(unsigned long)));
+}
+
+static int read_fd_string(int fd, const char *expected)
+{
+ char buf[128];
+ ssize_t nread;
+
+ nread = read(fd, buf, sizeof(buf) - 1);
+ if (nread < 0)
+ return -errno;
+
+ buf[nread] = '\0';
+ return strcmp(buf, expected) ? -EINVAL : 0;
+}
+
+static int write_file(const char *path, const char *data, mode_t mode)
+{
+ size_t left = strlen(data);
+ const char *p = data;
+ int fd;
+ int ret = 0;
+
+ fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, mode);
+ if (fd < 0)
+ return -errno;
+
+ while (left) {
+ ssize_t written = write(fd, p, left);
+
+ if (written < 0) {
+ ret = -errno;
+ break;
+ }
+ left -= written;
+ p += written;
+ }
+
+ close(fd);
+ return ret;
+}
+
+static int create_template_path(const char *path)
+{
+ struct spawn_template_create_args args = {
+ .flags = SPAWN_TEMPLATE_CREATE_CLOEXEC,
+ .execfd = -1,
+ .filename = (uintptr_t)path,
+ };
+
+ return syscall(__NR_spawn_template_create, &args, sizeof(args));
+}
+
+static int create_template_fd(int execfd)
+{
+ struct spawn_template_create_args args = {
+ .flags = SPAWN_TEMPLATE_CREATE_CLOEXEC,
+ .execfd = execfd,
+ };
+
+ return syscall(__NR_spawn_template_create, &args, sizeof(args));
+}
+
+static int spawn_template_start(int template_fd, char *const argv[],
+ struct spawn_template_action *actions,
+ unsigned int actions_len,
+ unsigned long long flags, pid_t *pid_out,
+ int *pidfd_out)
+{
+ char *const envp[] = { "PATH=/usr/bin:/bin", NULL };
+ struct spawn_template_spawn_args args = {
+ .flags = flags,
+ .argv = (uintptr_t)argv,
+ .envp = (uintptr_t)envp,
+ .actions = (uintptr_t)actions,
+ .actions_len = actions_len,
+ };
+ int pidfd = -1;
+ pid_t pid;
+ int ret;
+
+ args.pidfd = (uintptr_t)&pidfd;
+
+ pid = syscall(__NR_spawn_template_spawn, template_fd, &args,
+ sizeof(args));
+ if (pid < 0) {
+ ret = -errno;
+ if (pidfd >= 0) {
+ siginfo_t info;
+
+ waitid(P_PIDFD, pidfd, &info, WEXITED);
+ close(pidfd);
+ }
+ return ret;
+ }
+
+ *pid_out = pid;
+ *pidfd_out = pidfd;
+ return 0;
+}
+
+static int spawn_template(int template_fd, char *const argv[],
+ struct spawn_template_action *actions,
+ unsigned int actions_len, unsigned long long flags)
+{
+ siginfo_t info = {};
+ int pidfd;
+ pid_t pid;
+ int ret;
+
+ ret = spawn_template_start(template_fd, argv, actions, actions_len, flags,
+ &pid, &pidfd);
+ if (ret)
+ return ret;
+ (void)pid;
+
+ ret = waitid(P_PIDFD, pidfd, &info, WEXITED);
+ if (ret < 0) {
+ ret = -errno;
+ goto out_close_pidfd;
+ }
+
+ if (info.si_code != CLD_EXITED) {
+ ret = -EINVAL;
+ goto out_close_pidfd;
+ }
+
+ ret = info.si_status;
+
+out_close_pidfd:
+ if (pidfd >= 0)
+ close(pidfd);
+ return ret;
+}
+
+static const char *find_true(void)
+{
+ static const char * const paths[] = {
+ "/usr/bin/true",
+ "/bin/true",
+ };
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(paths); i++) {
+ if (access(paths[i], X_OK) == 0)
+ return paths[i];
+ }
+ return NULL;
+}
+
+static int copy_file(const char *src, const char *dst)
+{
+ char buf[8192];
+ ssize_t nread;
+ int infd;
+ int outfd;
+ int ret = 0;
+
+ infd = open(src, O_RDONLY | O_CLOEXEC);
+ if (infd < 0)
+ return -errno;
+
+ outfd = open(dst, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0700);
+ if (outfd < 0) {
+ ret = -errno;
+ goto out_close_in;
+ }
+
+ while ((nread = read(infd, buf, sizeof(buf))) > 0) {
+ char *p = buf;
+ ssize_t left = nread;
+
+ while (left > 0) {
+ ssize_t written = write(outfd, p, left);
+
+ if (written < 0) {
+ ret = -errno;
+ goto out_close_out;
+ }
+ left -= written;
+ p += written;
+ }
+ }
+ if (nread < 0)
+ ret = -errno;
+
+out_close_out:
+ close(outfd);
+out_close_in:
+ close(infd);
+ return ret;
+}
+
+static int test_basic_spawn(void)
+{
+ char *const argv[] = { (char *)true_path, NULL };
+ int template_fd;
+ int ret;
+
+ template_fd = create_template_path(true_path);
+ if (template_fd < 0)
+ return -errno;
+
+ ret = spawn_template(template_fd, argv, NULL, 0, 0);
+ close(template_fd);
+ return ret;
+}
+
+static int test_relative_path_rejected(void)
+{
+ int template_fd;
+
+ template_fd = create_template_path("true");
+ if (template_fd >= 0) {
+ close(template_fd);
+ return -EINVAL;
+ }
+
+ return errno == EINVAL ? 0 : -errno;
+}
+
+static int test_execfd_requires_execute(void)
+{
+ char path[] = "/tmp/spawn-template-noexec-XXXXXX";
+ int template_fd;
+ int fd;
+ int ret = 0;
+
+ fd = mkstemp(path);
+ if (fd < 0)
+ return -errno;
+
+ if (fchmod(fd, 0600)) {
+ ret = -errno;
+ goto out;
+ }
+
+ template_fd = create_template_fd(fd);
+ if (template_fd >= 0) {
+ close(template_fd);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = errno == EACCES ? 0 : -errno;
+
+out:
+ close(fd);
+ unlink(path);
+ return ret;
+}
+
+static int test_default_closes_extra_fds(void)
+{
+ char fdarg[32];
+ char *const argv[] = {
+ self_path,
+ "--check-fd-closed",
+ fdarg,
+ NULL,
+ };
+ int template_fd;
+ int extra_fd;
+ int ret;
+
+ extra_fd = open("/dev/null", O_RDONLY);
+ if (extra_fd < 0)
+ return -errno;
+
+ snprintf(fdarg, sizeof(fdarg), "%d", extra_fd);
+
+ template_fd = create_template_path(self_path);
+ if (template_fd < 0) {
+ ret = -errno;
+ goto out_close_extra;
+ }
+
+ ret = spawn_template(template_fd, argv, NULL, 0, 0);
+ close(template_fd);
+
+out_close_extra:
+ close(extra_fd);
+ return ret;
+}
+
+static int test_close_range_max_action(void)
+{
+ char fdarg[32];
+ char *const argv[] = {
+ self_path,
+ "--check-fd-closed",
+ fdarg,
+ NULL,
+ };
+ struct spawn_template_action action = {
+ .type = SPAWN_TEMPLATE_ACTION_CLOSE_RANGE,
+ .fd = -1,
+ .newfd = -1,
+ };
+ int template_fd;
+ int extra_fd;
+ int ret;
+
+ extra_fd = open("/dev/null", O_RDONLY | O_CLOEXEC);
+ if (extra_fd < 0)
+ return -errno;
+
+ action.fd = extra_fd;
+ snprintf(fdarg, sizeof(fdarg), "%d", extra_fd);
+
+ template_fd = create_template_path(self_path);
+ if (template_fd < 0) {
+ ret = -errno;
+ goto out_close_extra;
+ }
+
+ ret = spawn_template(template_fd, argv, &action, 1,
+ SPAWN_TEMPLATE_SPAWN_INHERIT_FDS);
+ close(template_fd);
+
+out_close_extra:
+ close(extra_fd);
+ return ret;
+}
+
+static int test_dup2_stdio_actions(void)
+{
+ char *const argv[] = { self_path, "--write-stdio", NULL };
+ struct spawn_template_action actions[2];
+ char out_buf[32];
+ char err_buf[32];
+ int out_pipe[2];
+ int err_pipe[2];
+ int template_fd;
+ int ret = 0;
+
+ if (pipe2(out_pipe, O_CLOEXEC))
+ return -errno;
+ if (pipe2(err_pipe, O_CLOEXEC)) {
+ ret = -errno;
+ goto out_close_out_pipe;
+ }
+
+ actions[0] = (struct spawn_template_action) {
+ .type = SPAWN_TEMPLATE_ACTION_DUP2,
+ .fd = out_pipe[1],
+ .newfd = STDOUT_FILENO,
+ };
+ actions[1] = (struct spawn_template_action) {
+ .type = SPAWN_TEMPLATE_ACTION_DUP2,
+ .fd = err_pipe[1],
+ .newfd = STDERR_FILENO,
+ };
+
+ template_fd = create_template_path(self_path);
+ if (template_fd < 0) {
+ ret = -errno;
+ goto out_close_err_pipe;
+ }
+
+ ret = spawn_template(template_fd, argv, actions, ARRAY_SIZE(actions), 0);
+ close(template_fd);
+ if (ret)
+ goto out_close_err_pipe;
+
+ close(out_pipe[1]);
+ out_pipe[1] = -1;
+ close(err_pipe[1]);
+ err_pipe[1] = -1;
+
+ memset(out_buf, 0, sizeof(out_buf));
+ memset(err_buf, 0, sizeof(err_buf));
+ if (read(out_pipe[0], out_buf, sizeof(out_buf) - 1) < 0) {
+ ret = -errno;
+ goto out_close_err_pipe;
+ }
+ if (read(err_pipe[0], err_buf, sizeof(err_buf) - 1) < 0) {
+ ret = -errno;
+ goto out_close_err_pipe;
+ }
+ if (strcmp(out_buf, "stdout-token\n") ||
+ strcmp(err_buf, "stderr-token\n"))
+ ret = -EINVAL;
+
+out_close_err_pipe:
+ if (err_pipe[1] >= 0)
+ close(err_pipe[1]);
+ close(err_pipe[0]);
+out_close_out_pipe:
+ if (out_pipe[1] >= 0)
+ close(out_pipe[1]);
+ close(out_pipe[0]);
+ return ret;
+}
+
+static int test_open_action_stdin(void)
+{
+ char dir[] = "/tmp/spawn-template-open-XXXXXX";
+ char path[PATH_MAX];
+ char *const argv[] = {
+ self_path,
+ "--check-fd-content",
+ "0",
+ "open-action-token\n",
+ NULL,
+ };
+ struct spawn_template_open open_arg = {
+ .path = (uintptr_t)path,
+ .how = {
+ .flags = O_RDONLY,
+ },
+ };
+ struct spawn_template_action action = {
+ .type = SPAWN_TEMPLATE_ACTION_OPEN,
+ .fd = AT_FDCWD,
+ .newfd = STDIN_FILENO,
+ .arg = (uintptr_t)&open_arg,
+ };
+ int template_fd;
+ int ret;
+
+ if (!mkdtemp(dir))
+ return -errno;
+
+ snprintf(path, sizeof(path), "%s/input", dir);
+ ret = write_file(path, "open-action-token\n", 0600);
+ if (ret)
+ goto out_unlink;
+
+ template_fd = create_template_path(self_path);
+ if (template_fd < 0) {
+ ret = -errno;
+ goto out_unlink;
+ }
+
+ ret = spawn_template(template_fd, argv, &action, 1, 0);
+ close(template_fd);
+
+out_unlink:
+ unlink(path);
+ rmdir(dir);
+ return ret;
+}
+
+static int test_fchdir_action(void)
+{
+ char dir[] = "/tmp/spawn-template-fchdir-XXXXXX";
+ char resolved[PATH_MAX];
+ char *const argv[] = {
+ self_path,
+ "--check-cwd",
+ resolved,
+ NULL,
+ };
+ struct spawn_template_action action = {
+ .type = SPAWN_TEMPLATE_ACTION_FCHDIR,
+ };
+ int template_fd;
+ int dirfd;
+ int ret;
+
+ if (!mkdtemp(dir))
+ return -errno;
+ if (!realpath(dir, resolved)) {
+ ret = -errno;
+ goto out_rmdir;
+ }
+
+ dirfd = open(dir, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+ if (dirfd < 0) {
+ ret = -errno;
+ goto out_rmdir;
+ }
+ action.fd = dirfd;
+
+ template_fd = create_template_path(self_path);
+ if (template_fd < 0) {
+ ret = -errno;
+ goto out_close_dirfd;
+ }
+
+ ret = spawn_template(template_fd, argv, &action, 1, 0);
+ close(template_fd);
+
+out_close_dirfd:
+ close(dirfd);
+out_rmdir:
+ rmdir(dir);
+ return ret;
+}
+
+static int test_sigmask_action(void)
+{
+ char sigarg[16];
+ char *const argv[] = {
+ self_path,
+ "--check-sigmask",
+ sigarg,
+ NULL,
+ };
+ struct spawn_template_kernel_sigset mask;
+ struct spawn_template_sigset sigset_arg = {
+ .sigset = (uintptr_t)&mask,
+ .sigsetsize = sizeof(mask),
+ };
+ struct spawn_template_action action = {
+ .type = SPAWN_TEMPLATE_ACTION_SIGMASK,
+ .arg = (uintptr_t)&sigset_arg,
+ };
+ int template_fd;
+ int ret;
+
+ spawn_template_kernel_sigempty(&mask);
+ spawn_template_kernel_sigadd(&mask, SIGUSR1);
+ snprintf(sigarg, sizeof(sigarg), "%d", SIGUSR1);
+
+ template_fd = create_template_path(self_path);
+ if (template_fd < 0)
+ return -errno;
+
+ ret = spawn_template(template_fd, argv, &action, 1, 0);
+ close(template_fd);
+ return ret;
+}
+
+static int test_sigdefault_action(void)
+{
+ char sigarg[16];
+ char *const argv[] = {
+ self_path,
+ "--check-sigdefault",
+ sigarg,
+ NULL,
+ };
+ struct spawn_template_kernel_sigset mask;
+ struct sigaction old_sa;
+ struct sigaction ignore_sa = {
+ .sa_handler = SIG_IGN,
+ };
+ struct spawn_template_sigset sigset_arg = {
+ .sigset = (uintptr_t)&mask,
+ .sigsetsize = sizeof(mask),
+ };
+ struct spawn_template_action action = {
+ .type = SPAWN_TEMPLATE_ACTION_SIGDEFAULT,
+ .arg = (uintptr_t)&sigset_arg,
+ };
+ int template_fd;
+ int ret;
+
+ spawn_template_kernel_sigempty(&mask);
+ spawn_template_kernel_sigadd(&mask, SIGUSR1);
+ snprintf(sigarg, sizeof(sigarg), "%d", SIGUSR1);
+
+ if (sigaction(SIGUSR1, &ignore_sa, &old_sa))
+ return -errno;
+
+ template_fd = create_template_path(self_path);
+ if (template_fd < 0) {
+ ret = -errno;
+ goto out_restore_signal;
+ }
+
+ ret = spawn_template(template_fd, argv, &action, 1, 0);
+ close(template_fd);
+
+out_restore_signal:
+ sigaction(SIGUSR1, &old_sa, NULL);
+ return ret;
+}
+
+static int test_inherit_fds_flag(void)
+{
+ char fdarg[32];
+ char *const argv[] = {
+ self_path,
+ "--check-fd-open",
+ fdarg,
+ NULL,
+ };
+ int template_fd;
+ int extra_fd;
+ int ret;
+
+ extra_fd = open("/dev/null", O_RDONLY);
+ if (extra_fd < 0)
+ return -errno;
+ snprintf(fdarg, sizeof(fdarg), "%d", extra_fd);
+
+ template_fd = create_template_path(self_path);
+ if (template_fd < 0) {
+ ret = -errno;
+ goto out_close_extra;
+ }
+
+ ret = spawn_template(template_fd, argv, NULL, 0,
+ SPAWN_TEMPLATE_SPAWN_INHERIT_FDS);
+ close(template_fd);
+
+out_close_extra:
+ close(extra_fd);
+ return ret;
+}
+
+static int test_pidfd_waitid(void)
+{
+ char *const argv[] = { (char *)true_path, NULL };
+ siginfo_t info = {};
+ int template_fd;
+ int pidfd;
+ pid_t pid;
+ int ret;
+
+ template_fd = create_template_path(true_path);
+ if (template_fd < 0)
+ return -errno;
+
+ ret = spawn_template_start(template_fd, argv, NULL, 0, 0, &pid, &pidfd);
+ close(template_fd);
+ if (ret)
+ return ret;
+
+ ret = waitid(P_PIDFD, pidfd, &info, WEXITED);
+ if (ret < 0) {
+ ret = -errno;
+ waitpid(pid, NULL, 0);
+ goto out_close_pidfd;
+ }
+ if (info.si_code != CLD_EXITED || info.si_status)
+ ret = -EINVAL;
+
+out_close_pidfd:
+ close(pidfd);
+ return ret;
+}
+
+static int test_create_actions_rejected(void)
+{
+ struct spawn_template_action action = {
+ .type = SPAWN_TEMPLATE_ACTION_CLOSE,
+ .fd = STDIN_FILENO,
+ };
+ struct spawn_template_create_args args = {
+ .flags = SPAWN_TEMPLATE_CREATE_CLOEXEC,
+ .execfd = -1,
+ .filename = (uintptr_t)true_path,
+ .actions = (uintptr_t)&action,
+ .actions_len = 1,
+ };
+ int template_fd;
+
+ template_fd = syscall(__NR_spawn_template_create, &args, sizeof(args));
+ if (template_fd >= 0) {
+ close(template_fd);
+ return -EINVAL;
+ }
+
+ return errno == EINVAL ? 0 : -errno;
+}
+
+static int test_script_template_unsupported(void)
+{
+ char dir[] = "/tmp/spawn-template-script-XXXXXX";
+ char path[PATH_MAX];
+ int template_fd;
+ int ret;
+
+ if (!mkdtemp(dir))
+ return -errno;
+
+ snprintf(path, sizeof(path), "%s/script", dir);
+ ret = write_file(path, "#!/bin/sh\nexit 0\n", 0700);
+ if (ret)
+ goto out_unlink;
+
+ template_fd = create_template_path(path);
+ if (template_fd >= 0) {
+ close(template_fd);
+ ret = -EINVAL;
+ goto out_unlink;
+ }
+ ret = errno == ENOEXEC ? 0 : -errno;
+
+out_unlink:
+ unlink(path);
+ rmdir(dir);
+ return ret;
+}
+
+static int test_deny_write_while_template_alive(void)
+{
+ char dir[] = "/tmp/spawn-template-deny-write-XXXXXX";
+ char path[PATH_MAX];
+ int template_fd;
+ int write_fd;
+ int ret = 0;
+
+ if (!mkdtemp(dir))
+ return -errno;
+
+ snprintf(path, sizeof(path), "%s/copy", dir);
+ ret = copy_file(self_path, path);
+ if (ret)
+ goto out_unlink;
+
+ template_fd = create_template_path(path);
+ if (template_fd < 0) {
+ ret = -errno;
+ goto out_unlink;
+ }
+
+ write_fd = open(path, O_WRONLY | O_TRUNC | O_CLOEXEC);
+ if (write_fd >= 0) {
+ close(write_fd);
+ ret = -EINVAL;
+ } else {
+ ret = errno == ETXTBSY ? 0 : -errno;
+ }
+
+ close(template_fd);
+out_unlink:
+ unlink(path);
+ rmdir(dir);
+ return ret;
+}
+
+static int test_stale_path_rejected(void)
+{
+ char dir[] = "/tmp/spawn-template-stale-XXXXXX";
+ char path[PATH_MAX];
+ char *const argv[] = { path, "--exit-zero", NULL };
+ int template_fd;
+ int ret = 0;
+
+ if (!mkdtemp(dir))
+ return -errno;
+
+ snprintf(path, sizeof(path), "%s/copy", dir);
+ ret = copy_file(self_path, path);
+ if (ret)
+ goto out_unlink;
+
+ template_fd = create_template_path(path);
+ if (template_fd < 0) {
+ ret = -errno;
+ goto out_unlink;
+ }
+
+ if (chmod(path, 0600)) {
+ ret = -errno;
+ goto out_close_template;
+ }
+
+ ret = spawn_template(template_fd, argv, NULL, 0, 0);
+ if (ret >= 0)
+ ret = -EINVAL;
+ else
+ ret = ret == -ESTALE ? 0 : ret;
+
+out_close_template:
+ close(template_fd);
+out_unlink:
+ unlink(path);
+ rmdir(dir);
+ return ret;
+}
+
+static int test_path_replacement_allows_tool_update(void)
+{
+ char dir[] = "/tmp/spawn-template-update-XXXXXX";
+ char path[PATH_MAX];
+ char new_path[PATH_MAX];
+ char *const argv[] = { path, "--exit-zero", NULL };
+ int new_template_fd = -1;
+ int template_fd = -1;
+ int ret;
+
+ if (!mkdtemp(dir))
+ return -errno;
+
+ snprintf(path, sizeof(path), "%s/tool", dir);
+ snprintf(new_path, sizeof(new_path), "%s/tool.new", dir);
+ ret = copy_file(self_path, path);
+ if (ret)
+ goto out;
+ ret = copy_file(self_path, new_path);
+ if (ret)
+ goto out;
+
+ template_fd = create_template_path(path);
+ if (template_fd < 0) {
+ ret = -errno;
+ goto out;
+ }
+
+ if (rename(new_path, path)) {
+ ret = -errno;
+ goto out;
+ }
+
+ ret = spawn_template(template_fd, argv, NULL, 0, 0);
+ if (ret != -ESTALE) {
+ ret = ret < 0 ? ret : -EINVAL;
+ goto out;
+ }
+
+ new_template_fd = create_template_path(path);
+ if (new_template_fd < 0) {
+ ret = -errno;
+ goto out;
+ }
+
+ ret = spawn_template(new_template_fd, argv, NULL, 0, 0);
+
+out:
+ if (new_template_fd >= 0)
+ close(new_template_fd);
+ if (template_fd >= 0)
+ close(template_fd);
+ unlink(new_path);
+ unlink(path);
+ rmdir(dir);
+ return ret;
+}
+
+static void run_test(const char *name, int (*fn)(void))
+{
+ int ret = fn();
+
+ if (!ret)
+ ksft_test_result_pass("%s\n", name);
+ else
+ ksft_test_result_fail("%s failed: %s (%d)\n",
+ name, strerror(-ret), -ret);
+}
+
+static void check_syscall_available(void)
+{
+ int template_fd;
+
+ template_fd = create_template_path(true_path);
+ if (template_fd >= 0) {
+ close(template_fd);
+ return;
+ }
+
+ if (errno == SPAWN_TEMPLATE_MISSING_SYSCALL_ERRNO)
+ ksft_exit_skip("spawn_template syscalls are not available\n");
+
+ ksft_exit_fail_msg("spawn_template_create failed: %s (%d)\n",
+ strerror(errno), errno);
+}
+
+int main(int argc, char **argv)
+{
+ ssize_t len;
+
+ if (argc == 2 && !strcmp(argv[1], "--exit-zero"))
+ return 0;
+
+ if (argc == 3 && !strcmp(argv[1], "--check-fd-closed")) {
+ int fd = atoi(argv[2]);
+
+ return fcntl(fd, F_GETFD) < 0 && errno == EBADF ? 0 : 1;
+ }
+
+ if (argc == 3 && !strcmp(argv[1], "--check-fd-open")) {
+ int fd = atoi(argv[2]);
+
+ return fcntl(fd, F_GETFD) >= 0 ? 0 : 1;
+ }
+
+ if (argc == 4 && !strcmp(argv[1], "--check-fd-content"))
+ return read_fd_string(atoi(argv[2]), argv[3]) ? 1 : 0;
+
+ if (argc == 3 && !strcmp(argv[1], "--check-cwd")) {
+ char cwd[PATH_MAX];
+
+ if (!getcwd(cwd, sizeof(cwd)))
+ return 1;
+ return strcmp(cwd, argv[2]) ? 1 : 0;
+ }
+
+ if (argc == 3 && !strcmp(argv[1], "--check-sigmask")) {
+ sigset_t mask;
+ int sig = atoi(argv[2]);
+
+ if (sigprocmask(SIG_BLOCK, NULL, &mask))
+ return 1;
+ return sigismember(&mask, sig) == 1 ? 0 : 1;
+ }
+
+ if (argc == 3 && !strcmp(argv[1], "--check-sigdefault")) {
+ struct sigaction sa;
+ int sig = atoi(argv[2]);
+
+ if (sigaction(sig, NULL, &sa))
+ return 1;
+ return sa.sa_handler == SIG_DFL ? 0 : 1;
+ }
+
+ if (argc == 2 && !strcmp(argv[1], "--write-stdio")) {
+ if (write(STDOUT_FILENO, "stdout-token\n", 13) != 13)
+ return 1;
+ if (write(STDERR_FILENO, "stderr-token\n", 13) != 13)
+ return 1;
+ return 0;
+ }
+
+ true_path = find_true();
+ if (!true_path)
+ ksft_exit_skip("could not find true executable\n");
+
+ len = readlink("/proc/self/exe", self_path, sizeof(self_path) - 1);
+ if (len < 0)
+ ksft_exit_fail_msg("readlink(/proc/self/exe) failed: %s\n",
+ strerror(errno));
+ self_path[len] = '\0';
+
+ check_syscall_available();
+
+ ksft_print_header();
+ ksft_set_plan(17);
+
+ run_test("basic spawn", test_basic_spawn);
+ run_test("relative path rejected", test_relative_path_rejected);
+ run_test("execfd execute permission checked",
+ test_execfd_requires_execute);
+ run_test("default fd close", test_default_closes_extra_fds);
+ run_test("close_range action max fd", test_close_range_max_action);
+ run_test("dup2 stdio actions", test_dup2_stdio_actions);
+ run_test("open action stdin", test_open_action_stdin);
+ run_test("fchdir action", test_fchdir_action);
+ run_test("sigmask action", test_sigmask_action);
+ run_test("sigdefault action", test_sigdefault_action);
+ run_test("inherit fds flag", test_inherit_fds_flag);
+ run_test("pidfd waitid", test_pidfd_waitid);
+ run_test("create-time actions rejected", test_create_actions_rejected);
+ run_test("script template unsupported", test_script_template_unsupported);
+ run_test("deny write while template alive",
+ test_deny_write_while_template_alive);
+ run_test("stale path rejected", test_stale_path_rejected);
+ run_test("path replacement allows tool update",
+ test_path_replacement_allows_tool_update);
+
+ ksft_finished();
+}
--
2.52.0
next prev parent reply other threads:[~2026-05-28 9:59 UTC|newest]
Thread overview: 20+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-28 9:52 [RFC PATCH v1 00/13] exec: add spawn templates for repeated executable startup Li Chen
2026-05-28 9:52 ` [RFC PATCH v1 01/13] exec: factor argument setup out of do_execveat_common() Li Chen
2026-05-28 9:52 ` [RFC PATCH v1 02/13] exec: add an internal helper for opened executables Li Chen
2026-05-28 9:52 ` [RFC PATCH v1 03/13] file: expose helpers for in-kernel fd actions Li Chen
2026-05-28 9:52 ` [RFC PATCH v1 04/13] exec: add spawn template UAPI definitions Li Chen
2026-05-28 9:52 ` [RFC PATCH v1 05/13] exec: add spawn template file descriptors Li Chen
2026-05-28 9:52 ` [RFC PATCH v1 06/13] exec: add spawn_template_spawn() Li Chen
2026-05-28 9:52 ` [RFC PATCH v1 07/13] exec: validate spawn template executable identity Li Chen
2026-05-28 9:52 ` [RFC PATCH v1 08/13] binfmt_elf: cache ELF metadata for spawn templates Li Chen
2026-05-28 9:52 ` [RFC PATCH v1 09/13] Documentation: describe " Li Chen
2026-05-28 9:52 ` [RFC PATCH v1 10/13] exec: require absolute paths for path-created templates Li Chen
2026-05-28 9:52 ` [RFC PATCH v1 11/13] exec: let close-range actions target the max fd Li Chen
2026-05-28 9:52 ` [RFC PATCH v1 12/13] syscalls: add generic spawn template entries Li Chen
2026-05-28 9:52 ` Li Chen [this message]
2026-05-28 11:02 ` [RFC PATCH v1 00/13] exec: add spawn templates for repeated executable startup Christian Brauner
2026-06-01 2:47 ` Li Chen
2026-06-01 19:55 ` Kees Cook
2026-05-28 12:55 ` Mateusz Guzik
2026-06-01 15:11 ` Li Chen
2026-05-28 18:27 ` Andy Lutomirski
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=20260528095235.2491226-14-me@linux.beauty \
--to=me@linux.beauty \
--cc=arnd@arndb.de \
--cc=bp@alien8.de \
--cc=brauner@kernel.org \
--cc=corbet@lwn.net \
--cc=dave.hansen@linux.intel.com \
--cc=hpa@zytor.com \
--cc=jack@suse.cz \
--cc=kees@kernel.org \
--cc=linux-api@vger.kernel.org \
--cc=linux-arch@vger.kernel.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=luto@kernel.org \
--cc=mingo@redhat.com \
--cc=skhan@linuxfoundation.org \
--cc=tglx@kernel.org \
--cc=viro@zeniv.linux.org.uk \
--cc=x86@kernel.org \
/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.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox