[RFC PATCH v1 13/13] selftests/exec: cover spawn template basics

Li Chen <[email protected]>
Newsgroups org.kernel.vger.linux-api,org.kernel.vger.linux-arch,org.kernel.vger.linux-doc,org.kernel.vger.linux-fsdevel,org.kernel.vger.linux-kernel,org.kernel.vger.linux-kselftest,org.kvack.linux-mm
Message-ID <[email protected]>
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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.