mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2025-01-10 07:44:23 +08:00
575a0ae974
As suggested by Andrew Morton in [1] add selftests for the new sys_pidfd_send_signal() syscall: /* test_pidfd_send_signal_syscall_support */ Test whether the pidfd_send_signal() syscall is supported and the tests can be run or need to be skipped. /* test_pidfd_send_signal_simple_success */ Test whether sending a signal via a pidfd works. /* test_pidfd_send_signal_exited_fail */ Verify that sending a signal to an already exited process fails with ESRCH. /* test_pidfd_send_signal_recycled_pid_fail */ Verify that a recycled pid cannot be signaled via a pidfd referring to an already exited process that had the same pid (cf. [2], [3]). [1]: https://lore.kernel.org/lkml/20181228152012.dbf0508c2508138efc5f2bbe@linux-foundation.org/ [2]: https://lore.kernel.org/lkml/20181230210245.GA30252@mail.hallyn.com/ [3]: https://lore.kernel.org/lkml/20181230232711.7aayb7vnhogbv4co@brauner.io/ Cc: Arnd Bergmann <arnd@arndb.de> Cc: "Eric W. Biederman" <ebiederm@xmission.com> Cc: Kees Cook <keescook@chromium.org> Cc: Jann Horn <jannh@google.com> Cc: Andy Lutomirsky <luto@kernel.org> Cc: Andrew Morton <akpm@linux-foundation.org> Cc: Oleg Nesterov <oleg@redhat.com> Cc: Aleksa Sarai <cyphar@cyphar.com> Cc: Al Viro <viro@zeniv.linux.org.uk> Cc: Florian Weimer <fweimer@redhat.com> Signed-off-by: Christian Brauner <christian@brauner.io> Reviewed-by: Tycho Andersen <tycho@tycho.ws> Acked-by: Serge Hallyn <serge@hallyn.com>
382 lines
8.5 KiB
C
382 lines
8.5 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
|
|
#define _GNU_SOURCE
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <linux/types.h>
|
|
#include <sched.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <syscall.h>
|
|
#include <sys/mount.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
|
|
#include "../kselftest.h"
|
|
|
|
static inline int sys_pidfd_send_signal(int pidfd, int sig, siginfo_t *info,
|
|
unsigned int flags)
|
|
{
|
|
return syscall(__NR_pidfd_send_signal, pidfd, sig, info, flags);
|
|
}
|
|
|
|
static int signal_received;
|
|
|
|
static void set_signal_received_on_sigusr1(int sig)
|
|
{
|
|
if (sig == SIGUSR1)
|
|
signal_received = 1;
|
|
}
|
|
|
|
/*
|
|
* Straightforward test to see whether pidfd_send_signal() works is to send
|
|
* a signal to ourself.
|
|
*/
|
|
static int test_pidfd_send_signal_simple_success(void)
|
|
{
|
|
int pidfd, ret;
|
|
const char *test_name = "pidfd_send_signal send SIGUSR1";
|
|
|
|
pidfd = open("/proc/self", O_DIRECTORY | O_CLOEXEC);
|
|
if (pidfd < 0)
|
|
ksft_exit_fail_msg(
|
|
"%s test: Failed to open process file descriptor\n",
|
|
test_name);
|
|
|
|
signal(SIGUSR1, set_signal_received_on_sigusr1);
|
|
|
|
ret = sys_pidfd_send_signal(pidfd, SIGUSR1, NULL, 0);
|
|
close(pidfd);
|
|
if (ret < 0)
|
|
ksft_exit_fail_msg("%s test: Failed to send signal\n",
|
|
test_name);
|
|
|
|
if (signal_received != 1)
|
|
ksft_exit_fail_msg("%s test: Failed to receive signal\n",
|
|
test_name);
|
|
|
|
signal_received = 0;
|
|
ksft_test_result_pass("%s test: Sent signal\n", test_name);
|
|
return 0;
|
|
}
|
|
|
|
static int wait_for_pid(pid_t pid)
|
|
{
|
|
int status, ret;
|
|
|
|
again:
|
|
ret = waitpid(pid, &status, 0);
|
|
if (ret == -1) {
|
|
if (errno == EINTR)
|
|
goto again;
|
|
|
|
return -1;
|
|
}
|
|
|
|
if (ret != pid)
|
|
goto again;
|
|
|
|
if (!WIFEXITED(status))
|
|
return -1;
|
|
|
|
return WEXITSTATUS(status);
|
|
}
|
|
|
|
static int test_pidfd_send_signal_exited_fail(void)
|
|
{
|
|
int pidfd, ret, saved_errno;
|
|
char buf[256];
|
|
pid_t pid;
|
|
const char *test_name = "pidfd_send_signal signal exited process";
|
|
|
|
pid = fork();
|
|
if (pid < 0)
|
|
ksft_exit_fail_msg("%s test: Failed to create new process\n",
|
|
test_name);
|
|
|
|
if (pid == 0)
|
|
_exit(EXIT_SUCCESS);
|
|
|
|
snprintf(buf, sizeof(buf), "/proc/%d", pid);
|
|
|
|
pidfd = open(buf, O_DIRECTORY | O_CLOEXEC);
|
|
|
|
(void)wait_for_pid(pid);
|
|
|
|
if (pidfd < 0)
|
|
ksft_exit_fail_msg(
|
|
"%s test: Failed to open process file descriptor\n",
|
|
test_name);
|
|
|
|
ret = sys_pidfd_send_signal(pidfd, 0, NULL, 0);
|
|
saved_errno = errno;
|
|
close(pidfd);
|
|
if (ret == 0)
|
|
ksft_exit_fail_msg(
|
|
"%s test: Managed to send signal to process even though it should have failed\n",
|
|
test_name);
|
|
|
|
if (saved_errno != ESRCH)
|
|
ksft_exit_fail_msg(
|
|
"%s test: Expected to receive ESRCH as errno value but received %d instead\n",
|
|
test_name, saved_errno);
|
|
|
|
ksft_test_result_pass("%s test: Failed to send signal as expected\n",
|
|
test_name);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* The kernel reserves 300 pids via RESERVED_PIDS in kernel/pid.c
|
|
* That means, when it wraps around any pid < 300 will be skipped.
|
|
* So we need to use a pid > 300 in order to test recycling.
|
|
*/
|
|
#define PID_RECYCLE 1000
|
|
|
|
/*
|
|
* Maximum number of cycles we allow. This is equivalent to PID_MAX_DEFAULT.
|
|
* If users set a higher limit or we have cycled PIDFD_MAX_DEFAULT number of
|
|
* times then we skip the test to not go into an infinite loop or block for a
|
|
* long time.
|
|
*/
|
|
#define PIDFD_MAX_DEFAULT 0x8000
|
|
|
|
/*
|
|
* Define a few custom error codes for the child process to clearly indicate
|
|
* what is happening. This way we can tell the difference between a system
|
|
* error, a test error, etc.
|
|
*/
|
|
#define PIDFD_PASS 0
|
|
#define PIDFD_FAIL 1
|
|
#define PIDFD_ERROR 2
|
|
#define PIDFD_SKIP 3
|
|
#define PIDFD_XFAIL 4
|
|
|
|
static int test_pidfd_send_signal_recycled_pid_fail(void)
|
|
{
|
|
int i, ret;
|
|
pid_t pid1;
|
|
const char *test_name = "pidfd_send_signal signal recycled pid";
|
|
|
|
ret = unshare(CLONE_NEWPID);
|
|
if (ret < 0)
|
|
ksft_exit_fail_msg("%s test: Failed to unshare pid namespace\n",
|
|
test_name);
|
|
|
|
ret = unshare(CLONE_NEWNS);
|
|
if (ret < 0)
|
|
ksft_exit_fail_msg(
|
|
"%s test: Failed to unshare mount namespace\n",
|
|
test_name);
|
|
|
|
ret = mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, 0);
|
|
if (ret < 0)
|
|
ksft_exit_fail_msg("%s test: Failed to remount / private\n",
|
|
test_name);
|
|
|
|
/* pid 1 in new pid namespace */
|
|
pid1 = fork();
|
|
if (pid1 < 0)
|
|
ksft_exit_fail_msg("%s test: Failed to create new process\n",
|
|
test_name);
|
|
|
|
if (pid1 == 0) {
|
|
char buf[256];
|
|
pid_t pid2;
|
|
int pidfd = -1;
|
|
|
|
(void)umount2("/proc", MNT_DETACH);
|
|
ret = mount("proc", "/proc", "proc", 0, NULL);
|
|
if (ret < 0)
|
|
_exit(PIDFD_ERROR);
|
|
|
|
/* grab pid PID_RECYCLE */
|
|
for (i = 0; i <= PIDFD_MAX_DEFAULT; i++) {
|
|
pid2 = fork();
|
|
if (pid2 < 0)
|
|
_exit(PIDFD_ERROR);
|
|
|
|
if (pid2 == 0)
|
|
_exit(PIDFD_PASS);
|
|
|
|
if (pid2 == PID_RECYCLE) {
|
|
snprintf(buf, sizeof(buf), "/proc/%d", pid2);
|
|
ksft_print_msg("pid to recycle is %d\n", pid2);
|
|
pidfd = open(buf, O_DIRECTORY | O_CLOEXEC);
|
|
}
|
|
|
|
if (wait_for_pid(pid2))
|
|
_exit(PIDFD_ERROR);
|
|
|
|
if (pid2 >= PID_RECYCLE)
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* We want to be as predictable as we can so if we haven't been
|
|
* able to grab pid PID_RECYCLE skip the test.
|
|
*/
|
|
if (pid2 != PID_RECYCLE) {
|
|
/* skip test */
|
|
close(pidfd);
|
|
_exit(PIDFD_SKIP);
|
|
}
|
|
|
|
if (pidfd < 0)
|
|
_exit(PIDFD_ERROR);
|
|
|
|
for (i = 0; i <= PIDFD_MAX_DEFAULT; i++) {
|
|
char c;
|
|
int pipe_fds[2];
|
|
pid_t recycled_pid;
|
|
int child_ret = PIDFD_PASS;
|
|
|
|
ret = pipe2(pipe_fds, O_CLOEXEC);
|
|
if (ret < 0)
|
|
_exit(PIDFD_ERROR);
|
|
|
|
recycled_pid = fork();
|
|
if (recycled_pid < 0)
|
|
_exit(PIDFD_ERROR);
|
|
|
|
if (recycled_pid == 0) {
|
|
close(pipe_fds[1]);
|
|
(void)read(pipe_fds[0], &c, 1);
|
|
close(pipe_fds[0]);
|
|
|
|
_exit(PIDFD_PASS);
|
|
}
|
|
|
|
/*
|
|
* Stop the child so we can inspect whether we have
|
|
* recycled pid PID_RECYCLE.
|
|
*/
|
|
close(pipe_fds[0]);
|
|
ret = kill(recycled_pid, SIGSTOP);
|
|
close(pipe_fds[1]);
|
|
if (ret) {
|
|
(void)wait_for_pid(recycled_pid);
|
|
_exit(PIDFD_ERROR);
|
|
}
|
|
|
|
/*
|
|
* We have recycled the pid. Try to signal it. This
|
|
* needs to fail since this is a different process than
|
|
* the one the pidfd refers to.
|
|
*/
|
|
if (recycled_pid == PID_RECYCLE) {
|
|
ret = sys_pidfd_send_signal(pidfd, SIGCONT,
|
|
NULL, 0);
|
|
if (ret && errno == ESRCH)
|
|
child_ret = PIDFD_XFAIL;
|
|
else
|
|
child_ret = PIDFD_FAIL;
|
|
}
|
|
|
|
/* let the process move on */
|
|
ret = kill(recycled_pid, SIGCONT);
|
|
if (ret)
|
|
(void)kill(recycled_pid, SIGKILL);
|
|
|
|
if (wait_for_pid(recycled_pid))
|
|
_exit(PIDFD_ERROR);
|
|
|
|
switch (child_ret) {
|
|
case PIDFD_FAIL:
|
|
/* fallthrough */
|
|
case PIDFD_XFAIL:
|
|
_exit(child_ret);
|
|
case PIDFD_PASS:
|
|
break;
|
|
default:
|
|
/* not reached */
|
|
_exit(PIDFD_ERROR);
|
|
}
|
|
|
|
/*
|
|
* If the user set a custom pid_max limit we could be
|
|
* in the millions.
|
|
* Skip the test in this case.
|
|
*/
|
|
if (recycled_pid > PIDFD_MAX_DEFAULT)
|
|
_exit(PIDFD_SKIP);
|
|
}
|
|
|
|
/* failed to recycle pid */
|
|
_exit(PIDFD_SKIP);
|
|
}
|
|
|
|
ret = wait_for_pid(pid1);
|
|
switch (ret) {
|
|
case PIDFD_FAIL:
|
|
ksft_exit_fail_msg(
|
|
"%s test: Managed to signal recycled pid %d\n",
|
|
test_name, PID_RECYCLE);
|
|
case PIDFD_PASS:
|
|
ksft_exit_fail_msg("%s test: Failed to recycle pid %d\n",
|
|
test_name, PID_RECYCLE);
|
|
case PIDFD_SKIP:
|
|
ksft_print_msg("%s test: Skipping test\n", test_name);
|
|
ret = 0;
|
|
break;
|
|
case PIDFD_XFAIL:
|
|
ksft_test_result_pass(
|
|
"%s test: Failed to signal recycled pid as expected\n",
|
|
test_name);
|
|
ret = 0;
|
|
break;
|
|
default /* PIDFD_ERROR */:
|
|
ksft_exit_fail_msg("%s test: Error while running tests\n",
|
|
test_name);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int test_pidfd_send_signal_syscall_support(void)
|
|
{
|
|
int pidfd, ret;
|
|
const char *test_name = "pidfd_send_signal check for support";
|
|
|
|
pidfd = open("/proc/self", O_DIRECTORY | O_CLOEXEC);
|
|
if (pidfd < 0)
|
|
ksft_exit_fail_msg(
|
|
"%s test: Failed to open process file descriptor\n",
|
|
test_name);
|
|
|
|
ret = sys_pidfd_send_signal(pidfd, 0, NULL, 0);
|
|
if (ret < 0) {
|
|
/*
|
|
* pidfd_send_signal() will currently return ENOSYS when
|
|
* CONFIG_PROC_FS is not set.
|
|
*/
|
|
if (errno == ENOSYS)
|
|
ksft_exit_skip(
|
|
"%s test: pidfd_send_signal() syscall not supported (Ensure that CONFIG_PROC_FS=y is set)\n",
|
|
test_name);
|
|
|
|
ksft_exit_fail_msg("%s test: Failed to send signal\n",
|
|
test_name);
|
|
}
|
|
|
|
close(pidfd);
|
|
ksft_test_result_pass(
|
|
"%s test: pidfd_send_signal() syscall is supported. Tests can be executed\n",
|
|
test_name);
|
|
return 0;
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
ksft_print_header();
|
|
|
|
test_pidfd_send_signal_syscall_support();
|
|
test_pidfd_send_signal_simple_success();
|
|
test_pidfd_send_signal_exited_fail();
|
|
test_pidfd_send_signal_recycled_pid_fail();
|
|
|
|
return ksft_exit_pass();
|
|
}
|