Add tests for concurrent closing of a poll/select monitored fd.
authorvisa <visa@openbsd.org>
Sun, 21 Nov 2021 06:21:01 +0000 (06:21 +0000)
committervisa <visa@openbsd.org>
Sun, 21 Nov 2021 06:21:01 +0000 (06:21 +0000)
regress/sys/kern/Makefile
regress/sys/kern/poll/Makefile
regress/sys/kern/poll/poll_close.c [new file with mode: 0644]
regress/sys/kern/select/Makefile [new file with mode: 0644]
regress/sys/kern/select/select_close.c [new file with mode: 0644]

index 3df716e..b3c3c0d 100644 (file)
@@ -1,4 +1,4 @@
-#      $OpenBSD: Makefile,v 1.93 2021/11/19 17:14:38 mvs Exp $
+#      $OpenBSD: Makefile,v 1.94 2021/11/21 06:21:01 visa Exp $
 
 SUBDIR+= __syscall
 SUBDIR+= accept access
@@ -15,6 +15,7 @@ SUBDIR+= nanosleep noexec
 SUBDIR+= open
 SUBDIR+= pipe pledge poll pread preadv ptmget ptrace ptrace2 pty pwrite pwritev
 SUBDIR+= rcvtimeo realpath realpath-unmount recvwait rlimit-file
+SUBDIR+= select
 # The setuid subtest creates set user/group id binaries in the obj directory.
 # Do not run this test by default, it may trigger alerts from daily security.
 .ifmake clean || cleandir || obj
index 5b9265b..3b87d79 100644 (file)
@@ -1,5 +1,7 @@
-#      $OpenBSD: Makefile,v 1.1 2021/10/29 13:13:04 mpi Exp $
+#      $OpenBSD: Makefile,v 1.2 2021/11/21 06:21:01 visa Exp $
 
-PROG=  pollnval
+PROGS=         poll_close pollnval
+LDADD=         -lpthread
+WARNINGS=      yes
 
 .include <bsd.regress.mk>
diff --git a/regress/sys/kern/poll/poll_close.c b/regress/sys/kern/poll/poll_close.c
new file mode 100644 (file)
index 0000000..a05b0c6
--- /dev/null
@@ -0,0 +1,166 @@
+/*     $OpenBSD: poll_close.c,v 1.1 2021/11/21 06:21:01 visa Exp $     */
+
+/*
+ * Copyright (c) 2021 Visa Hankala
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Test behaviour when a monitored file descriptor is closed by another thread.
+ *
+ * Note that this case is not defined by POSIX.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <assert.h>
+#include <err.h>
+#include <poll.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+static int     barrier[2];
+static int     sock[2];
+
+static int
+wait_wchan(const char *wchanname)
+{
+       struct kinfo_proc kps[3]; /* process + 2 threads */
+       struct timespec end, now;
+       size_t size;
+       unsigned int i;
+       int mib[6], ret;
+
+       clock_gettime(CLOCK_MONOTONIC, &now);
+       end = now;
+       end.tv_sec += 1;
+
+       mib[0] = CTL_KERN;
+       mib[1] = KERN_PROC;
+       mib[2] = KERN_PROC_PID | KERN_PROC_SHOW_THREADS;
+       mib[3] = getpid();
+       mib[4] = sizeof(kps[0]);
+       mib[5] = sizeof(kps) / sizeof(kps[0]);
+
+       for (;;) {
+               memset(kps, 0, sizeof(kps));
+               size = sizeof(kps);
+               ret = sysctl(mib, 6, kps, &size, NULL, 0);
+               if (ret == -1)
+                       err(1, "sysctl");
+               for (i = 0; i < size / sizeof(kps[0]); i++) {
+                       if (strncmp(kps[i].p_wmesg, wchanname,
+                           sizeof(kps[i].p_wmesg)) == 0)
+                               return 0;
+               }
+
+               usleep(1000);
+               clock_gettime(CLOCK_MONOTONIC, &now);
+               if (timespeccmp(&now, &end, >=))
+                       break;
+       }
+
+       errx(1, "wchan %s timeout", wchanname);
+}
+
+static void *
+thread_main(void *arg)
+{
+       struct pollfd pfd[1];
+       int ret;
+       char b;
+
+       memset(pfd, 0, sizeof(pfd));
+       pfd[0].fd = sock[1];
+       pfd[0].events = POLLIN;
+       ret = poll(pfd, 1, 100);
+       assert(ret == 1);
+       assert(pfd[0].revents & POLLIN);
+
+       /* Drain data to prevent subsequent wakeups. */
+       read(sock[1], &b, 1);
+
+       /* Sync with parent thread. */
+       write(barrier[1], "y", 1);
+       read(barrier[1], &b, 1);
+
+       memset(pfd, 0, sizeof(pfd));
+       pfd[0].fd = sock[1];
+       pfd[0].events = POLLIN;
+       ret = poll(pfd, 1, 100);
+       assert(ret == 1);
+       assert(pfd[0].revents & POLLNVAL);
+
+       return NULL;
+}
+
+int
+main(void)
+{
+       pthread_t t;
+       int ret, saved_fd;
+       char b;
+
+       /* Enforce test timeout. */
+       alarm(10);
+
+       if (socketpair(AF_UNIX, SOCK_STREAM, 0, barrier) == -1)
+               err(1, "can't create socket pair");
+
+       if (socketpair(AF_UNIX, SOCK_STREAM, 0, sock) == -1)
+               err(1, "can't create socket pair");
+
+       ret = pthread_create(&t, NULL, thread_main, NULL);
+       if (ret != 0) {
+               fprintf(stderr, "can't start thread: %s\n", strerror(ret));
+               return 1;
+       }
+
+       /* Let the thread settle in poll(). */
+       wait_wchan("kqread");
+
+       /* Awaken poll(). */
+       write(sock[0], "x", 1);
+
+       /* Wait until the thread has left poll(). */
+       read(barrier[0], &b, 1);
+
+       /*
+        * Close and restore the fd that the thread has polled.
+        * This creates a pending badfd knote in the kernel.
+        */
+       saved_fd = dup(sock[1]);
+       close(sock[1]);
+       dup2(saved_fd, sock[1]);
+       close(saved_fd);
+
+       /* Let the thread continue. */
+       write(barrier[0], "x", 1);
+
+       /* Let the thread settle in poll(). */
+       wait_wchan("kqread");
+
+       /* Close the fd to awaken poll(). */
+       close(sock[1]);
+
+       pthread_join(t, NULL);
+
+       close(sock[0]);
+
+       return 0;
+}
diff --git a/regress/sys/kern/select/Makefile b/regress/sys/kern/select/Makefile
new file mode 100644 (file)
index 0000000..9de4216
--- /dev/null
@@ -0,0 +1,7 @@
+#      $OpenBSD: Makefile,v 1.1 2021/11/21 06:21:01 visa Exp $
+
+PROGS=         select_close
+LDADD=         -lpthread
+WARNINGS=      yes
+
+.include <bsd.regress.mk>
diff --git a/regress/sys/kern/select/select_close.c b/regress/sys/kern/select/select_close.c
new file mode 100644 (file)
index 0000000..b06a5a2
--- /dev/null
@@ -0,0 +1,170 @@
+/*     $OpenBSD: select_close.c,v 1.1 2021/11/21 06:21:01 visa Exp $   */
+
+/*
+ * Copyright (c) 2021 Visa Hankala
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Test behaviour when a monitored file descriptor is closed by another thread.
+ *
+ * Note that this case is not defined by POSIX.
+ */
+
+#include <sys/types.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+static int     barrier[2];
+static int     sock[2];
+
+static int
+wait_wchan(const char *wchanname)
+{
+       struct kinfo_proc kps[3]; /* process + 2 threads */
+       struct timespec end, now;
+       size_t size;
+       unsigned int i;
+       int mib[6], ret;
+
+       clock_gettime(CLOCK_MONOTONIC, &now);
+       end = now;
+       end.tv_sec += 1;
+
+       mib[0] = CTL_KERN;
+       mib[1] = KERN_PROC;
+       mib[2] = KERN_PROC_PID | KERN_PROC_SHOW_THREADS;
+       mib[3] = getpid();
+       mib[4] = sizeof(kps[0]);
+       mib[5] = sizeof(kps) / sizeof(kps[0]);
+
+       for (;;) {
+               memset(kps, 0, sizeof(kps));
+               size = sizeof(kps);
+               ret = sysctl(mib, 6, kps, &size, NULL, 0);
+               if (ret == -1)
+                       err(1, "sysctl");
+               for (i = 0; i < size / sizeof(kps[0]); i++) {
+                       if (strncmp(kps[i].p_wmesg, wchanname,
+                           sizeof(kps[i].p_wmesg)) == 0)
+                               return 0;
+               }
+
+               usleep(1000);
+               clock_gettime(CLOCK_MONOTONIC, &now);
+               if (timespeccmp(&now, &end, >=))
+                       break;
+       }
+
+       errx(1, "wchan %s timeout", wchanname);
+}
+
+static void *
+thread_main(void *arg)
+{
+       fd_set rfds;
+       struct timeval tv;
+       int ret;
+       char b;
+
+       FD_ZERO(&rfds);
+       FD_SET(sock[1], &rfds);
+       tv.tv_sec = 0;
+       tv.tv_usec = 100000;
+       ret = select(sock[1] + 1, &rfds, NULL, NULL, &tv);
+       assert(ret == 1);
+       assert(FD_ISSET(sock[1], &rfds));
+
+       /* Drain data to prevent subsequent wakeups. */
+       read(sock[1], &b, 1);
+
+       /* Sync with parent thread. */
+       write(barrier[1], "y", 1);
+       read(barrier[1], &b, 1);
+
+       FD_ZERO(&rfds);
+       FD_SET(sock[1], &rfds);
+       tv.tv_sec = 0;
+       tv.tv_usec = 100000;
+       ret = select(sock[1] + 1, &rfds, NULL, NULL, &tv);
+       assert(ret == -1);
+       assert(errno == EBADF);
+
+       return NULL;
+}
+
+int
+main(void)
+{
+       pthread_t t;
+       int ret, saved_fd;
+       char b;
+
+       /* Enforce test timeout. */
+       alarm(10);
+
+       if (socketpair(AF_UNIX, SOCK_STREAM, 0, barrier) == -1)
+               err(1, "can't create socket pair");
+
+       if (socketpair(AF_UNIX, SOCK_STREAM, 0, sock) == -1)
+               err(1, "can't create socket pair");
+
+       ret = pthread_create(&t, NULL, thread_main, NULL);
+       if (ret != 0) {
+               fprintf(stderr, "can't start thread: %s\n", strerror(ret));
+               return 1;
+       }
+
+       /* Let the thread settle in select(). */
+       wait_wchan("kqread");
+
+       /* Awaken poll(). */
+       write(sock[0], "x", 1);
+
+       /* Wait until the thread has left select(). */
+       read(barrier[0], &b, 1);
+
+       /*
+        * Close and restore the fd that the thread has polled.
+        * This creates a pending badfd knote in the kernel.
+        */
+       saved_fd = dup(sock[1]);
+       close(sock[1]);
+       dup2(saved_fd, sock[1]);
+       close(saved_fd);
+
+       /* Let the thread continue. */
+       write(barrier[0], "x", 1);
+
+       /* Let the thread settle in select(). */
+       wait_wchan("kqread");
+
+       /* Close the fd to awaken select(). */
+       close(sock[1]);
+
+       pthread_join(t, NULL);
+
+       close(sock[0]);
+
+       return 0;
+}