Page MenuHomeFreeBSD

D47738.diff
No OneTemporary

D47738.diff

diff --git a/tests/sys/kern/Makefile b/tests/sys/kern/Makefile
--- a/tests/sys/kern/Makefile
+++ b/tests/sys/kern/Makefile
@@ -23,6 +23,7 @@
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
diff --git a/tests/sys/kern/prace.c b/tests/sys/kern/prace.c
new file mode 100644
--- /dev/null
+++ b/tests/sys/kern/prace.c
@@ -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());
+}

File Metadata

Mime Type
text/plain
Expires
Thu, Jan 16, 5:03 AM (19 h, 52 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
15820342
Default Alt Text
D47738.diff (3 KB)

Event Timeline