diff mbox series

[v7,2/2] selftests: add tests for pidfd_send_signal()

Message ID 20190102161654.9093-2-christian@brauner.io (mailing list archive)
State New, archived
Headers show
Series [v7,1/2] signal: add pidfd_send_signal() syscall | expand

Commit Message

Christian Brauner Jan. 2, 2019, 4:16 p.m. UTC
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>
Acked-by: Serge Hallyn <serge@hallyn.com>
---
/* Changelog */
v7:
- add test for pid recycling (cf. [2] and [3]) as suggested by Serge
- add test for pidfd_send_signal() syscalls support so we can skip tests if
  it isn't
v6:
- patch introduced
v5..v0:
- patch not present
---
 tools/testing/selftests/Makefile           |   1 +
 tools/testing/selftests/pidfd/Makefile     |   6 +
 tools/testing/selftests/pidfd/pidfd_test.c | 381 +++++++++++++++++++++
 3 files changed, 388 insertions(+)
 create mode 100644 tools/testing/selftests/pidfd/Makefile
 create mode 100644 tools/testing/selftests/pidfd/pidfd_test.c

Comments

Tycho Andersen Jan. 8, 2019, 5:53 p.m. UTC | #1
On Wed, Jan 02, 2019 at 05:16:54PM +0100, Christian Brauner wrote:
> +			/*
> +			 * 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);
> +			}

Sorry for being late to the party, but I wonder if this whole thing
couldn't be simplified with /proc/sys/kenrel/ns_last_pid?

Tycho
Serge E. Hallyn Jan. 8, 2019, 5:54 p.m. UTC | #2
On Tue, Jan 08, 2019 at 10:53:06AM -0700, Tycho Andersen wrote:
> On Wed, Jan 02, 2019 at 05:16:54PM +0100, Christian Brauner wrote:
> > +			/*
> > +			 * 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);
> > +			}
> 
> Sorry for being late to the party, but I wonder if this whole thing
> couldn't be simplified with /proc/sys/kenrel/ns_last_pid?

no, bc it's not namespaced :)
Tycho Andersen Jan. 8, 2019, 5:58 p.m. UTC | #3
On Tue, Jan 08, 2019 at 11:54:15AM -0600, Serge E. Hallyn wrote:
> On Tue, Jan 08, 2019 at 10:53:06AM -0700, Tycho Andersen wrote:
> > On Wed, Jan 02, 2019 at 05:16:54PM +0100, Christian Brauner wrote:
> > > +			/*
> > > +			 * 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);
> > > +			}
> > 
> > Sorry for being late to the party, but I wonder if this whole thing
> > couldn't be simplified with /proc/sys/kenrel/ns_last_pid?
> 
> no, bc it's not namespaced :)

Huh? It looks like it is...

static int pid_ns_ctl_handler(struct ctl_table *table, int write,
                void __user *buffer, size_t *lenp, loff_t *ppos)
{
        struct pid_namespace *pid_ns = task_active_pid_ns(current);
        struct ctl_table tmp = *table;
        int ret, next;

        if (write && !ns_capable(pid_ns->user_ns, CAP_SYS_ADMIN))
                return -EPERM;

        ...

Tycho
Serge E. Hallyn Jan. 8, 2019, 6:17 p.m. UTC | #4
On Tue, Jan 08, 2019 at 10:58:43AM -0700, Tycho Andersen wrote:
> On Tue, Jan 08, 2019 at 11:54:15AM -0600, Serge E. Hallyn wrote:
> > On Tue, Jan 08, 2019 at 10:53:06AM -0700, Tycho Andersen wrote:
> > > On Wed, Jan 02, 2019 at 05:16:54PM +0100, Christian Brauner wrote:
> > > > +			/*
> > > > +			 * 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);
> > > > +			}
> > > 
> > > Sorry for being late to the party, but I wonder if this whole thing
> > > couldn't be simplified with /proc/sys/kenrel/ns_last_pid?
> > 
> > no, bc it's not namespaced :)
> 
> Huh? It looks like it is...
> 
> static int pid_ns_ctl_handler(struct ctl_table *table, int write,
>                 void __user *buffer, size_t *lenp, loff_t *ppos)
> {
>         struct pid_namespace *pid_ns = task_active_pid_ns(current);
>         struct ctl_table tmp = *table;
>         int ret, next;
> 
>         if (write && !ns_capable(pid_ns->user_ns, CAP_SYS_ADMIN))
>                 return -EPERM;
> 
>         ...

Oh - hah, but that's ns_last_pid.  You'd want pid_max.  And that one
is not namespaced.
Tycho Andersen Jan. 8, 2019, 6:20 p.m. UTC | #5
On Tue, Jan 08, 2019 at 12:17:42PM -0600, Serge E. Hallyn wrote:
> On Tue, Jan 08, 2019 at 10:58:43AM -0700, Tycho Andersen wrote:
> > On Tue, Jan 08, 2019 at 11:54:15AM -0600, Serge E. Hallyn wrote:
> > > On Tue, Jan 08, 2019 at 10:53:06AM -0700, Tycho Andersen wrote:
> > > > On Wed, Jan 02, 2019 at 05:16:54PM +0100, Christian Brauner wrote:
> > > > > +			/*
> > > > > +			 * 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);
> > > > > +			}
> > > > 
> > > > Sorry for being late to the party, but I wonder if this whole thing
> > > > couldn't be simplified with /proc/sys/kenrel/ns_last_pid?
> > > 
> > > no, bc it's not namespaced :)
> > 
> > Huh? It looks like it is...
> > 
> > static int pid_ns_ctl_handler(struct ctl_table *table, int write,
> >                 void __user *buffer, size_t *lenp, loff_t *ppos)
> > {
> >         struct pid_namespace *pid_ns = task_active_pid_ns(current);
> >         struct ctl_table tmp = *table;
> >         int ret, next;
> > 
> >         if (write && !ns_capable(pid_ns->user_ns, CAP_SYS_ADMIN))
> >                 return -EPERM;
> > 
> >         ...
> 
> Oh - hah, but that's ns_last_pid.  You'd want pid_max.  And that one
> is not namespaced.

Perhaps I'm misunderstanding, but isn't the point of all this code to
get the same pid again? So can't we just fork(), kill(), then set
ns_last_pid to pid-1, and fork() again to re-use?

Tycho
Serge E. Hallyn Jan. 8, 2019, 6:21 p.m. UTC | #6
On Tue, Jan 08, 2019 at 11:20:23AM -0700, Tycho Andersen wrote:
> On Tue, Jan 08, 2019 at 12:17:42PM -0600, Serge E. Hallyn wrote:
> > On Tue, Jan 08, 2019 at 10:58:43AM -0700, Tycho Andersen wrote:
> > > On Tue, Jan 08, 2019 at 11:54:15AM -0600, Serge E. Hallyn wrote:
> > > > On Tue, Jan 08, 2019 at 10:53:06AM -0700, Tycho Andersen wrote:
> > > > > On Wed, Jan 02, 2019 at 05:16:54PM +0100, Christian Brauner wrote:
> > > > > > +			/*
> > > > > > +			 * 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);
> > > > > > +			}
> > > > > 
> > > > > Sorry for being late to the party, but I wonder if this whole thing
> > > > > couldn't be simplified with /proc/sys/kenrel/ns_last_pid?
> > > > 
> > > > no, bc it's not namespaced :)
> > > 
> > > Huh? It looks like it is...
> > > 
> > > static int pid_ns_ctl_handler(struct ctl_table *table, int write,
> > >                 void __user *buffer, size_t *lenp, loff_t *ppos)
> > > {
> > >         struct pid_namespace *pid_ns = task_active_pid_ns(current);
> > >         struct ctl_table tmp = *table;
> > >         int ret, next;
> > > 
> > >         if (write && !ns_capable(pid_ns->user_ns, CAP_SYS_ADMIN))
> > >                 return -EPERM;
> > > 
> > >         ...
> > 
> > Oh - hah, but that's ns_last_pid.  You'd want pid_max.  And that one
> > is not namespaced.
> 
> Perhaps I'm misunderstanding, but isn't the point of all this code to
> get the same pid again? So can't we just fork(), kill(), then set
> ns_last_pid to pid-1, and fork() again to re-use?

Oh yeah that would work :)

I was stuck on the idea of just limiting the range of pids.
Christian Brauner Jan. 8, 2019, 6:24 p.m. UTC | #7
On Tue, Jan 08, 2019 at 11:20:23AM -0700, Tycho Andersen wrote:
> On Tue, Jan 08, 2019 at 12:17:42PM -0600, Serge E. Hallyn wrote:
> > On Tue, Jan 08, 2019 at 10:58:43AM -0700, Tycho Andersen wrote:
> > > On Tue, Jan 08, 2019 at 11:54:15AM -0600, Serge E. Hallyn wrote:
> > > > On Tue, Jan 08, 2019 at 10:53:06AM -0700, Tycho Andersen wrote:
> > > > > On Wed, Jan 02, 2019 at 05:16:54PM +0100, Christian Brauner wrote:
> > > > > > +			/*
> > > > > > +			 * 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);
> > > > > > +			}
> > > > > 
> > > > > Sorry for being late to the party, but I wonder if this whole thing
> > > > > couldn't be simplified with /proc/sys/kenrel/ns_last_pid?
> > > > 
> > > > no, bc it's not namespaced :)
> > > 
> > > Huh? It looks like it is...
> > > 
> > > static int pid_ns_ctl_handler(struct ctl_table *table, int write,
> > >                 void __user *buffer, size_t *lenp, loff_t *ppos)
> > > {
> > >         struct pid_namespace *pid_ns = task_active_pid_ns(current);
> > >         struct ctl_table tmp = *table;
> > >         int ret, next;
> > > 
> > >         if (write && !ns_capable(pid_ns->user_ns, CAP_SYS_ADMIN))
> > >                 return -EPERM;
> > > 
> > >         ...
> > 
> > Oh - hah, but that's ns_last_pid.  You'd want pid_max.  And that one
> > is not namespaced.
> 
> Perhaps I'm misunderstanding, but isn't the point of all this code to
> get the same pid again? So can't we just fork(), kill(), then set
> ns_last_pid to pid-1, and fork() again to re-use?

Maybe. It's just a selftest that works reliably as it is so unless
there's a technical issue with the patch I'm not going to do another
version just because of that unless people feel super strongly about
this.
Another advantage is that the code we have right now works even when
CONFIG_CHECKPOINT_RESTORE is not selected.
Tycho Andersen Jan. 8, 2019, 6:25 p.m. UTC | #8
On Tue, Jan 08, 2019 at 07:24:46PM +0100, Christian Brauner wrote:
> On Tue, Jan 08, 2019 at 11:20:23AM -0700, Tycho Andersen wrote:
> > On Tue, Jan 08, 2019 at 12:17:42PM -0600, Serge E. Hallyn wrote:
> > > On Tue, Jan 08, 2019 at 10:58:43AM -0700, Tycho Andersen wrote:
> > > > On Tue, Jan 08, 2019 at 11:54:15AM -0600, Serge E. Hallyn wrote:
> > > > > On Tue, Jan 08, 2019 at 10:53:06AM -0700, Tycho Andersen wrote:
> > > > > > On Wed, Jan 02, 2019 at 05:16:54PM +0100, Christian Brauner wrote:
> > > > > > > +			/*
> > > > > > > +			 * 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);
> > > > > > > +			}
> > > > > > 
> > > > > > Sorry for being late to the party, but I wonder if this whole thing
> > > > > > couldn't be simplified with /proc/sys/kenrel/ns_last_pid?
> > > > > 
> > > > > no, bc it's not namespaced :)
> > > > 
> > > > Huh? It looks like it is...
> > > > 
> > > > static int pid_ns_ctl_handler(struct ctl_table *table, int write,
> > > >                 void __user *buffer, size_t *lenp, loff_t *ppos)
> > > > {
> > > >         struct pid_namespace *pid_ns = task_active_pid_ns(current);
> > > >         struct ctl_table tmp = *table;
> > > >         int ret, next;
> > > > 
> > > >         if (write && !ns_capable(pid_ns->user_ns, CAP_SYS_ADMIN))
> > > >                 return -EPERM;
> > > > 
> > > >         ...
> > > 
> > > Oh - hah, but that's ns_last_pid.  You'd want pid_max.  And that one
> > > is not namespaced.
> > 
> > Perhaps I'm misunderstanding, but isn't the point of all this code to
> > get the same pid again? So can't we just fork(), kill(), then set
> > ns_last_pid to pid-1, and fork() again to re-use?
> 
> Maybe. It's just a selftest that works reliably as it is so unless
> there's a technical issue with the patch I'm not going to do another
> version just because of that unless people feel super strongly about
> this.
> Another advantage is that the code we have right now works even when
> CONFIG_CHECKPOINT_RESTORE is not selected.

No, it's fine as is. Just a lot less code if we do it the other way.

Cheers,

Tycho
diff mbox series

Patch

diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index eb54df682d56..84dd78a684e4 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -29,6 +29,7 @@  TARGETS += net
 TARGETS += netfilter
 TARGETS += networking/timestamping
 TARGETS += nsfs
+TARGETS += pidfd
 TARGETS += powerpc
 TARGETS += proc
 TARGETS += pstore
diff --git a/tools/testing/selftests/pidfd/Makefile b/tools/testing/selftests/pidfd/Makefile
new file mode 100644
index 000000000000..deaf8073bc06
--- /dev/null
+++ b/tools/testing/selftests/pidfd/Makefile
@@ -0,0 +1,6 @@ 
+CFLAGS += -g -I../../../../usr/include/
+
+TEST_GEN_PROGS := pidfd_test
+
+include ../lib.mk
+
diff --git a/tools/testing/selftests/pidfd/pidfd_test.c b/tools/testing/selftests/pidfd/pidfd_test.c
new file mode 100644
index 000000000000..d59378a93782
--- /dev/null
+++ b/tools/testing/selftests/pidfd/pidfd_test.c
@@ -0,0 +1,381 @@ 
+/* 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();
+}