mirror of
https://github.com/opnsense/src.git
synced 2026-06-12 10:10:24 -04:00
tests: Add regression test for ppoll() / pselect() race.
These tests demonstrate the bug that was fixed in ccb973da1f.
Sponsored by: Klara, Inc.
Sponsored by: NetApp, Inc.
Reviewed by: markj
Differential Revision: https://reviews.freebsd.org/D47738
This commit is contained in:
parent
31e796c86d
commit
6748d4e0eb
2 changed files with 145 additions and 0 deletions
|
|
@ -23,6 +23,7 @@ ATF_TESTS_C+= ktls_test
|
|||
ATF_TESTS_C+= ktrace_test
|
||||
ATF_TESTS_C+= listener_wakeup
|
||||
ATF_TESTS_C+= module_test
|
||||
ATF_TESTS_C+= prace
|
||||
ATF_TESTS_C+= ptrace_test
|
||||
TEST_METADATA.ptrace_test+= timeout="15"
|
||||
ATF_TESTS_C+= reaper
|
||||
|
|
|
|||
144
tests/sys/kern/prace.c
Normal file
144
tests/sys/kern/prace.c
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
/*-
|
||||
* Copyright (c) 2024 Klara, Inc.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*
|
||||
* These tests demonstrate a bug in ppoll() and pselect() where a blocked
|
||||
* signal can fire after the timer runs out but before the signal mask is
|
||||
* restored. To do this, we fork a child process which installs a SIGINT
|
||||
* handler and repeatedly calls either ppoll() or pselect() with a 1 ms
|
||||
* timeout, while the parent repeatedly sends SIGINT to the child at
|
||||
* intervals that start out at 1100 us and gradually decrease to 900 us.
|
||||
* Each SIGINT resynchronizes parent and child, and sooner or later the
|
||||
* parent hits the sweet spot and the SIGINT arrives at just the right
|
||||
* time to demonstrate the bug.
|
||||
*/
|
||||
|
||||
#include <sys/select.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <poll.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <atf-c.h>
|
||||
|
||||
static volatile sig_atomic_t caught[NSIG];
|
||||
|
||||
static void
|
||||
handler(int signo)
|
||||
{
|
||||
caught[signo]++;
|
||||
}
|
||||
|
||||
static void
|
||||
child(int rd, bool poll)
|
||||
{
|
||||
struct timespec timeout = { .tv_nsec = 1000000 };
|
||||
sigset_t set0, set1;
|
||||
int ret;
|
||||
|
||||
/* empty mask for ppoll() / pselect() */
|
||||
sigemptyset(&set0);
|
||||
|
||||
/* block SIGINT, then install a handler for it */
|
||||
sigemptyset(&set1);
|
||||
sigaddset(&set1, SIGINT);
|
||||
sigprocmask(SIG_BLOCK, &set1, NULL);
|
||||
signal(SIGINT, handler);
|
||||
|
||||
/* signal parent that we are ready */
|
||||
close(rd);
|
||||
for (;;) {
|
||||
/* sleep for 1 ms with signals unblocked */
|
||||
ret = poll ? ppoll(NULL, 0, &timeout, &set0) :
|
||||
pselect(0, NULL, NULL, NULL, &timeout, &set0);
|
||||
/*
|
||||
* At this point, either ret == 0 (timer ran out) errno ==
|
||||
* EINTR (a signal was received). Any other outcome is
|
||||
* abnormal.
|
||||
*/
|
||||
if (ret != 0 && errno != EINTR)
|
||||
err(1, "p%s()", poll ? "poll" : "select");
|
||||
/* if ret == 0, we should not have caught any signals */
|
||||
if (ret == 0 && caught[SIGINT]) {
|
||||
/*
|
||||
* We successfully demonstrated the race. Restore
|
||||
* the default action and re-raise SIGINT.
|
||||
*/
|
||||
signal(SIGINT, SIG_DFL);
|
||||
raise(SIGINT);
|
||||
/* Not reached */
|
||||
}
|
||||
/* reset for next attempt */
|
||||
caught[SIGINT] = 0;
|
||||
}
|
||||
/* Not reached */
|
||||
}
|
||||
|
||||
static void
|
||||
prace(bool poll)
|
||||
{
|
||||
int pd[2], status;
|
||||
pid_t pid;
|
||||
|
||||
/* fork child process */
|
||||
if (pipe(pd) != 0)
|
||||
err(1, "pipe()");
|
||||
if ((pid = fork()) < 0)
|
||||
err(1, "fork()");
|
||||
if (pid == 0) {
|
||||
close(pd[0]);
|
||||
child(pd[1], poll);
|
||||
/* Not reached */
|
||||
}
|
||||
close(pd[1]);
|
||||
|
||||
/* wait for child to signal readiness */
|
||||
(void)read(pd[0], &pd[0], sizeof(pd[0]));
|
||||
close(pd[0]);
|
||||
|
||||
/* repeatedly attempt to signal at just the right moment */
|
||||
for (useconds_t timeout = 1100; timeout > 900; timeout--) {
|
||||
usleep(timeout);
|
||||
if (kill(pid, SIGINT) != 0) {
|
||||
if (errno != ENOENT)
|
||||
err(1, "kill()");
|
||||
/* ENOENT means the child has terminated */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* we're done, kill the child for sure */
|
||||
(void)kill(pid, SIGKILL);
|
||||
if (waitpid(pid, &status, 0) < 0)
|
||||
err(1, "waitpid()");
|
||||
|
||||
/* assert that the child died of SIGKILL */
|
||||
ATF_REQUIRE(WIFSIGNALED(status));
|
||||
ATF_REQUIRE_MSG(WTERMSIG(status) == SIGKILL,
|
||||
"child caught SIG%s", sys_signame[WTERMSIG(status)]);
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(ppoll_race);
|
||||
ATF_TC_BODY(ppoll_race, tc)
|
||||
{
|
||||
prace(true);
|
||||
}
|
||||
|
||||
ATF_TC_WITHOUT_HEAD(pselect_race);
|
||||
ATF_TC_BODY(pselect_race, tc)
|
||||
{
|
||||
prace(false);
|
||||
}
|
||||
|
||||
ATF_TP_ADD_TCS(tp)
|
||||
{
|
||||
ATF_TP_ADD_TC(tp, ppoll_race);
|
||||
ATF_TP_ADD_TC(tp, pselect_race);
|
||||
return (atf_no_error());
|
||||
}
|
||||
Loading…
Reference in a new issue