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 <[email protected]> --- 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

