--- sys/dev/netmap/netmap_freebsd.c.orig +++ sys/dev/netmap/netmap_freebsd.c @@ -119,6 +119,7 @@ taskqueue_drain(si->ntfytq, &si->ntfytask); taskqueue_free(si->ntfytq); si->ntfytq = NULL; + seldrain(&si->si); knlist_delete(&si->si.si_note, curthread, /*islocked=*/0); knlist_destroy(&si->si.si_note); /* now we don't need the mutex anymore */ --- sys/kern/sys_procdesc.c.orig +++ sys/kern/sys_procdesc.c @@ -273,6 +273,7 @@ KASSERT((pd->pd_flags & PDF_CLOSED), ("procdesc_free: !PDF_CLOSED")); + seldrain(&pd->pd_selinfo); knlist_destroy(&pd->pd_selinfo.si_note); PROCDESC_LOCK_DESTROY(pd); free(pd, M_PROCDESC); @@ -315,10 +316,7 @@ procdesc_free(pd); return (1); } - if (pd->pd_flags & PDF_SELECTED) { - pd->pd_flags &= ~PDF_SELECTED; - selwakeup(&pd->pd_selinfo); - } + selwakeup(&pd->pd_selinfo); KNOTE_LOCKED(&pd->pd_selinfo.si_note, NOTE_EXIT); PROCDESC_UNLOCK(pd); return (0); @@ -433,10 +431,8 @@ PROCDESC_LOCK(pd); if (pd->pd_flags & PDF_EXITED) revents |= POLLHUP; - if (revents == 0) { + else selrecord(td, &pd->pd_selinfo); - pd->pd_flags |= PDF_SELECTED; - } PROCDESC_UNLOCK(pd); return (revents); } --- sys/sys/procdesc.h.orig +++ sys/sys/procdesc.h @@ -86,7 +86,6 @@ * Flags for the pd_flags field. */ #define PDF_CLOSED 0x00000001 /* Descriptor has closed. */ -#define PDF_SELECTED 0x00000002 /* Issue selwakeup(). */ #define PDF_EXITED 0x00000004 /* Process exited. */ #define PDF_DAEMON 0x00000008 /* Don't exit when procdesc closes. */ --- tests/sys/kern/Makefile.orig +++ tests/sys/kern/Makefile @@ -27,6 +27,7 @@ .endif ATF_TESTS_C+= ktrace_test ATF_TESTS_C+= module_test +ATF_TESTS_C+= procdesc ATF_TESTS_C+= ptrace_test TEST_METADATA.ptrace_test+= timeout="15" ATF_TESTS_C+= reaper @@ -85,6 +86,7 @@ LIBADD.kcov+= pthread CFLAGS.ktls_test+= -DOPENSSL_API_COMPAT=0x10100000L LIBADD.ktls_test+= crypto util +LIBADD.procdesc+= pthread LIBADD.socket_msg_waitall+= pthread LIBADD.socket_splice+= pthread LIBADD.sendfile_helper+= pthread --- /dev/null +++ tests/sys/kern/procdesc.c @@ -0,0 +1,128 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2026 ConnectWise + * Copyright (c) 2026 Mark Johnston + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +/* Tests for procdesc(4) that aren't specific to any one syscall */ + +static void * +poll_procdesc(void *arg) +{ + struct pollfd pfd; + + pfd.fd = *(int *)arg; + pfd.events = POLLHUP; + (void)poll(&pfd, 1, 5000); + return ((void *)(uintptr_t)pfd.revents); +} + +/* + * Regression test to exercise the case where a procdesc is closed while a + * thread is poll()ing it. + */ +ATF_TC_WITHOUT_HEAD(poll_close_race); +ATF_TC_BODY(poll_close_race, tc) +{ + pthread_t thr; + pid_t pid; + uintptr_t revents; + int error, pd; + + pid = pdfork(&pd, PD_DAEMON); + ATF_REQUIRE_MSG(pid >= 0, "pdfork: %s", strerror(errno)); + if (pid == 0) { + pause(); + _exit(0); + } + + error = pthread_create(&thr, NULL, poll_procdesc, &pd); + ATF_REQUIRE_MSG(error == 0, "pthread_create: %s", strerror(error)); + + /* Wait for the thread to block in poll(2). */ + usleep(250000); + + ATF_REQUIRE_MSG(close(pd) == 0, "close: %s", strerror(errno)); + + error = pthread_join(thr, (void *)&revents); + ATF_REQUIRE_MSG(error == 0, "pthread_join: %s", strerror(error)); + ATF_REQUIRE_EQ(revents, POLLNVAL); +} + +/* + * Verify that poll(2) of a procdesc returns POLLHUP when the process exits. + */ +ATF_TC_WITHOUT_HEAD(poll_exit_wakeup); +ATF_TC_BODY(poll_exit_wakeup, tc) +{ + pthread_t thr; + uintptr_t revents; + pid_t pid; + int error, pd; + + pid = pdfork(&pd, PD_DAEMON); + ATF_REQUIRE_MSG(pid >= 0, "pdfork: %s", strerror(errno)); + if (pid == 0) { + pause(); + _exit(0); + } + + error = pthread_create(&thr, NULL, poll_procdesc, &pd); + ATF_REQUIRE_MSG(error == 0, "pthread_create: %s", strerror(error)); + + /* Wait for the thread to block in poll(2). */ + usleep(250000); + + ATF_REQUIRE_MSG(pdkill(pd, SIGKILL) == 0, + "pdkill: %s", strerror(errno)); + + error = pthread_join(thr, (void *)&revents); + ATF_REQUIRE_MSG(error == 0, "pthread_join: %s", strerror(error)); + ATF_REQUIRE_EQ(revents, POLLHUP); + + ATF_REQUIRE_MSG(close(pd) == 0, "close: %s", strerror(errno)); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, poll_close_race); + ATF_TP_ADD_TC(tp, poll_exit_wakeup); + + return (atf_no_error()); +}