Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F107507460
D47738.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
3 KB
Referenced Files
None
Subscribers
None
D47738.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D47738: tests: Demonstrate a bug in ppoll() and pselect().
Attached
Detach File
Event Timeline
Log In to Comment