Skip to content

Commit 575a0ae

Browse files
committed
selftests: add tests for pidfd_send_signal()
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>
1 parent 3eb39f4 commit 575a0ae

File tree

3 files changed

+388
-0
lines changed

3 files changed

+388
-0
lines changed

tools/testing/selftests/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ TARGETS += net
3030
TARGETS += netfilter
3131
TARGETS += networking/timestamping
3232
TARGETS += nsfs
33+
TARGETS += pidfd
3334
TARGETS += powerpc
3435
TARGETS += proc
3536
TARGETS += pstore
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
CFLAGS += -g -I../../../../usr/include/
2+
3+
TEST_GEN_PROGS := pidfd_test
4+
5+
include ../lib.mk
6+
Lines changed: 381 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,381 @@
1+
/* SPDX-License-Identifier: GPL-2.0 */
2+
3+
#define _GNU_SOURCE
4+
#include <errno.h>
5+
#include <fcntl.h>
6+
#include <linux/types.h>
7+
#include <sched.h>
8+
#include <signal.h>
9+
#include <stdio.h>
10+
#include <stdlib.h>
11+
#include <string.h>
12+
#include <syscall.h>
13+
#include <sys/mount.h>
14+
#include <sys/wait.h>
15+
#include <unistd.h>
16+
17+
#include "../kselftest.h"
18+
19+
static inline int sys_pidfd_send_signal(int pidfd, int sig, siginfo_t *info,
20+
unsigned int flags)
21+
{
22+
return syscall(__NR_pidfd_send_signal, pidfd, sig, info, flags);
23+
}
24+
25+
static int signal_received;
26+
27+
static void set_signal_received_on_sigusr1(int sig)
28+
{
29+
if (sig == SIGUSR1)
30+
signal_received = 1;
31+
}
32+
33+
/*
34+
* Straightforward test to see whether pidfd_send_signal() works is to send
35+
* a signal to ourself.
36+
*/
37+
static int test_pidfd_send_signal_simple_success(void)
38+
{
39+
int pidfd, ret;
40+
const char *test_name = "pidfd_send_signal send SIGUSR1";
41+
42+
pidfd = open("/proc/self", O_DIRECTORY | O_CLOEXEC);
43+
if (pidfd < 0)
44+
ksft_exit_fail_msg(
45+
"%s test: Failed to open process file descriptor\n",
46+
test_name);
47+
48+
signal(SIGUSR1, set_signal_received_on_sigusr1);
49+
50+
ret = sys_pidfd_send_signal(pidfd, SIGUSR1, NULL, 0);
51+
close(pidfd);
52+
if (ret < 0)
53+
ksft_exit_fail_msg("%s test: Failed to send signal\n",
54+
test_name);
55+
56+
if (signal_received != 1)
57+
ksft_exit_fail_msg("%s test: Failed to receive signal\n",
58+
test_name);
59+
60+
signal_received = 0;
61+
ksft_test_result_pass("%s test: Sent signal\n", test_name);
62+
return 0;
63+
}
64+
65+
static int wait_for_pid(pid_t pid)
66+
{
67+
int status, ret;
68+
69+
again:
70+
ret = waitpid(pid, &status, 0);
71+
if (ret == -1) {
72+
if (errno == EINTR)
73+
goto again;
74+
75+
return -1;
76+
}
77+
78+
if (ret != pid)
79+
goto again;
80+
81+
if (!WIFEXITED(status))
82+
return -1;
83+
84+
return WEXITSTATUS(status);
85+
}
86+
87+
static int test_pidfd_send_signal_exited_fail(void)
88+
{
89+
int pidfd, ret, saved_errno;
90+
char buf[256];
91+
pid_t pid;
92+
const char *test_name = "pidfd_send_signal signal exited process";
93+
94+
pid = fork();
95+
if (pid < 0)
96+
ksft_exit_fail_msg("%s test: Failed to create new process\n",
97+
test_name);
98+
99+
if (pid == 0)
100+
_exit(EXIT_SUCCESS);
101+
102+
snprintf(buf, sizeof(buf), "/proc/%d", pid);
103+
104+
pidfd = open(buf, O_DIRECTORY | O_CLOEXEC);
105+
106+
(void)wait_for_pid(pid);
107+
108+
if (pidfd < 0)
109+
ksft_exit_fail_msg(
110+
"%s test: Failed to open process file descriptor\n",
111+
test_name);
112+
113+
ret = sys_pidfd_send_signal(pidfd, 0, NULL, 0);
114+
saved_errno = errno;
115+
close(pidfd);
116+
if (ret == 0)
117+
ksft_exit_fail_msg(
118+
"%s test: Managed to send signal to process even though it should have failed\n",
119+
test_name);
120+
121+
if (saved_errno != ESRCH)
122+
ksft_exit_fail_msg(
123+
"%s test: Expected to receive ESRCH as errno value but received %d instead\n",
124+
test_name, saved_errno);
125+
126+
ksft_test_result_pass("%s test: Failed to send signal as expected\n",
127+
test_name);
128+
return 0;
129+
}
130+
131+
/*
132+
* The kernel reserves 300 pids via RESERVED_PIDS in kernel/pid.c
133+
* That means, when it wraps around any pid < 300 will be skipped.
134+
* So we need to use a pid > 300 in order to test recycling.
135+
*/
136+
#define PID_RECYCLE 1000
137+
138+
/*
139+
* Maximum number of cycles we allow. This is equivalent to PID_MAX_DEFAULT.
140+
* If users set a higher limit or we have cycled PIDFD_MAX_DEFAULT number of
141+
* times then we skip the test to not go into an infinite loop or block for a
142+
* long time.
143+
*/
144+
#define PIDFD_MAX_DEFAULT 0x8000
145+
146+
/*
147+
* Define a few custom error codes for the child process to clearly indicate
148+
* what is happening. This way we can tell the difference between a system
149+
* error, a test error, etc.
150+
*/
151+
#define PIDFD_PASS 0
152+
#define PIDFD_FAIL 1
153+
#define PIDFD_ERROR 2
154+
#define PIDFD_SKIP 3
155+
#define PIDFD_XFAIL 4
156+
157+
static int test_pidfd_send_signal_recycled_pid_fail(void)
158+
{
159+
int i, ret;
160+
pid_t pid1;
161+
const char *test_name = "pidfd_send_signal signal recycled pid";
162+
163+
ret = unshare(CLONE_NEWPID);
164+
if (ret < 0)
165+
ksft_exit_fail_msg("%s test: Failed to unshare pid namespace\n",
166+
test_name);
167+
168+
ret = unshare(CLONE_NEWNS);
169+
if (ret < 0)
170+
ksft_exit_fail_msg(
171+
"%s test: Failed to unshare mount namespace\n",
172+
test_name);
173+
174+
ret = mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, 0);
175+
if (ret < 0)
176+
ksft_exit_fail_msg("%s test: Failed to remount / private\n",
177+
test_name);
178+
179+
/* pid 1 in new pid namespace */
180+
pid1 = fork();
181+
if (pid1 < 0)
182+
ksft_exit_fail_msg("%s test: Failed to create new process\n",
183+
test_name);
184+
185+
if (pid1 == 0) {
186+
char buf[256];
187+
pid_t pid2;
188+
int pidfd = -1;
189+
190+
(void)umount2("/proc", MNT_DETACH);
191+
ret = mount("proc", "/proc", "proc", 0, NULL);
192+
if (ret < 0)
193+
_exit(PIDFD_ERROR);
194+
195+
/* grab pid PID_RECYCLE */
196+
for (i = 0; i <= PIDFD_MAX_DEFAULT; i++) {
197+
pid2 = fork();
198+
if (pid2 < 0)
199+
_exit(PIDFD_ERROR);
200+
201+
if (pid2 == 0)
202+
_exit(PIDFD_PASS);
203+
204+
if (pid2 == PID_RECYCLE) {
205+
snprintf(buf, sizeof(buf), "/proc/%d", pid2);
206+
ksft_print_msg("pid to recycle is %d\n", pid2);
207+
pidfd = open(buf, O_DIRECTORY | O_CLOEXEC);
208+
}
209+
210+
if (wait_for_pid(pid2))
211+
_exit(PIDFD_ERROR);
212+
213+
if (pid2 >= PID_RECYCLE)
214+
break;
215+
}
216+
217+
/*
218+
* We want to be as predictable as we can so if we haven't been
219+
* able to grab pid PID_RECYCLE skip the test.
220+
*/
221+
if (pid2 != PID_RECYCLE) {
222+
/* skip test */
223+
close(pidfd);
224+
_exit(PIDFD_SKIP);
225+
}
226+
227+
if (pidfd < 0)
228+
_exit(PIDFD_ERROR);
229+
230+
for (i = 0; i <= PIDFD_MAX_DEFAULT; i++) {
231+
char c;
232+
int pipe_fds[2];
233+
pid_t recycled_pid;
234+
int child_ret = PIDFD_PASS;
235+
236+
ret = pipe2(pipe_fds, O_CLOEXEC);
237+
if (ret < 0)
238+
_exit(PIDFD_ERROR);
239+
240+
recycled_pid = fork();
241+
if (recycled_pid < 0)
242+
_exit(PIDFD_ERROR);
243+
244+
if (recycled_pid == 0) {
245+
close(pipe_fds[1]);
246+
(void)read(pipe_fds[0], &c, 1);
247+
close(pipe_fds[0]);
248+
249+
_exit(PIDFD_PASS);
250+
}
251+
252+
/*
253+
* Stop the child so we can inspect whether we have
254+
* recycled pid PID_RECYCLE.
255+
*/
256+
close(pipe_fds[0]);
257+
ret = kill(recycled_pid, SIGSTOP);
258+
close(pipe_fds[1]);
259+
if (ret) {
260+
(void)wait_for_pid(recycled_pid);
261+
_exit(PIDFD_ERROR);
262+
}
263+
264+
/*
265+
* We have recycled the pid. Try to signal it. This
266+
* needs to fail since this is a different process than
267+
* the one the pidfd refers to.
268+
*/
269+
if (recycled_pid == PID_RECYCLE) {
270+
ret = sys_pidfd_send_signal(pidfd, SIGCONT,
271+
NULL, 0);
272+
if (ret && errno == ESRCH)
273+
child_ret = PIDFD_XFAIL;
274+
else
275+
child_ret = PIDFD_FAIL;
276+
}
277+
278+
/* let the process move on */
279+
ret = kill(recycled_pid, SIGCONT);
280+
if (ret)
281+
(void)kill(recycled_pid, SIGKILL);
282+
283+
if (wait_for_pid(recycled_pid))
284+
_exit(PIDFD_ERROR);
285+
286+
switch (child_ret) {
287+
case PIDFD_FAIL:
288+
/* fallthrough */
289+
case PIDFD_XFAIL:
290+
_exit(child_ret);
291+
case PIDFD_PASS:
292+
break;
293+
default:
294+
/* not reached */
295+
_exit(PIDFD_ERROR);
296+
}
297+
298+
/*
299+
* If the user set a custom pid_max limit we could be
300+
* in the millions.
301+
* Skip the test in this case.
302+
*/
303+
if (recycled_pid > PIDFD_MAX_DEFAULT)
304+
_exit(PIDFD_SKIP);
305+
}
306+
307+
/* failed to recycle pid */
308+
_exit(PIDFD_SKIP);
309+
}
310+
311+
ret = wait_for_pid(pid1);
312+
switch (ret) {
313+
case PIDFD_FAIL:
314+
ksft_exit_fail_msg(
315+
"%s test: Managed to signal recycled pid %d\n",
316+
test_name, PID_RECYCLE);
317+
case PIDFD_PASS:
318+
ksft_exit_fail_msg("%s test: Failed to recycle pid %d\n",
319+
test_name, PID_RECYCLE);
320+
case PIDFD_SKIP:
321+
ksft_print_msg("%s test: Skipping test\n", test_name);
322+
ret = 0;
323+
break;
324+
case PIDFD_XFAIL:
325+
ksft_test_result_pass(
326+
"%s test: Failed to signal recycled pid as expected\n",
327+
test_name);
328+
ret = 0;
329+
break;
330+
default /* PIDFD_ERROR */:
331+
ksft_exit_fail_msg("%s test: Error while running tests\n",
332+
test_name);
333+
}
334+
335+
return ret;
336+
}
337+
338+
static int test_pidfd_send_signal_syscall_support(void)
339+
{
340+
int pidfd, ret;
341+
const char *test_name = "pidfd_send_signal check for support";
342+
343+
pidfd = open("/proc/self", O_DIRECTORY | O_CLOEXEC);
344+
if (pidfd < 0)
345+
ksft_exit_fail_msg(
346+
"%s test: Failed to open process file descriptor\n",
347+
test_name);
348+
349+
ret = sys_pidfd_send_signal(pidfd, 0, NULL, 0);
350+
if (ret < 0) {
351+
/*
352+
* pidfd_send_signal() will currently return ENOSYS when
353+
* CONFIG_PROC_FS is not set.
354+
*/
355+
if (errno == ENOSYS)
356+
ksft_exit_skip(
357+
"%s test: pidfd_send_signal() syscall not supported (Ensure that CONFIG_PROC_FS=y is set)\n",
358+
test_name);
359+
360+
ksft_exit_fail_msg("%s test: Failed to send signal\n",
361+
test_name);
362+
}
363+
364+
close(pidfd);
365+
ksft_test_result_pass(
366+
"%s test: pidfd_send_signal() syscall is supported. Tests can be executed\n",
367+
test_name);
368+
return 0;
369+
}
370+
371+
int main(int argc, char **argv)
372+
{
373+
ksft_print_header();
374+
375+
test_pidfd_send_signal_syscall_support();
376+
test_pidfd_send_signal_simple_success();
377+
test_pidfd_send_signal_exited_fail();
378+
test_pidfd_send_signal_recycled_pid_fail();
379+
380+
return ksft_exit_pass();
381+
}

0 commit comments

Comments
 (0)