From 79d51bdc5258c0c94e5c55bd49a6c759f92d3d69 Mon Sep 17 00:00:00 2001 From: visa Date: Sun, 21 Nov 2021 06:21:01 +0000 Subject: [PATCH] Add tests for concurrent closing of a poll/select monitored fd. --- regress/sys/kern/Makefile | 3 +- regress/sys/kern/poll/Makefile | 6 +- regress/sys/kern/poll/poll_close.c | 166 ++++++++++++++++++++++++ regress/sys/kern/select/Makefile | 7 + regress/sys/kern/select/select_close.c | 170 +++++++++++++++++++++++++ 5 files changed, 349 insertions(+), 3 deletions(-) create mode 100644 regress/sys/kern/poll/poll_close.c create mode 100644 regress/sys/kern/select/Makefile create mode 100644 regress/sys/kern/select/select_close.c diff --git a/regress/sys/kern/Makefile b/regress/sys/kern/Makefile index 3df716ed91a..b3c3c0da71a 100644 --- a/regress/sys/kern/Makefile +++ b/regress/sys/kern/Makefile @@ -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 diff --git a/regress/sys/kern/poll/Makefile b/regress/sys/kern/poll/Makefile index 5b9265b9586..3b87d79221c 100644 --- a/regress/sys/kern/poll/Makefile +++ b/regress/sys/kern/poll/Makefile @@ -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 diff --git a/regress/sys/kern/poll/poll_close.c b/regress/sys/kern/poll/poll_close.c new file mode 100644 index 00000000000..a05b0c69b1e --- /dev/null +++ b/regress/sys/kern/poll/poll_close.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 index 00000000000..9de4216906e --- /dev/null +++ b/regress/sys/kern/select/Makefile @@ -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 diff --git a/regress/sys/kern/select/select_close.c b/regress/sys/kern/select/select_close.c new file mode 100644 index 00000000000..b06a5a2cb6d --- /dev/null +++ b/regress/sys/kern/select/select_close.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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; +} -- 2.20.1