From nobody Wed May 20 19:39:29 2026 X-Original-To: dev-commits-src-all@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4gLMLV0mQZz6fJgy for ; Wed, 20 May 2026 19:39:30 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R13" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id 4gLMLT54Sqz3Xbf for ; Wed, 20 May 2026 19:39:29 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1779305969; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=i7M4Kygpg4aPYIwJGk9VUJnUOZ8nlo33isUD8GJ8ECg=; b=kdFxqIdXB43l8OLu3qsCYfuefWPpqPY+TjXAaf0ntBWPjvodaAH4YA5FxhpJbl9Wz3mbsp dl6YpMA9o2J1l0m7/Ohj1qKGnw5TBN5XFtuQ60YB5rOQ91jHvCH2FOne4Mxo+K0YhF+39i xNh1uQYP+a6vUvLsQltynI9anLcaCyihnvKxP1oQcLXNw2XGb4ayl/+iOUlHeqTmV/ycm+ yyOTRVKa8AfDZPQBXV8VXGZhODxR/AhxT7pF7MYIcWX2ionxEkLlbZUk1rRNYrk0vpiALe V8p/e6751URaUh1ocY9JQfkCb8iNcPfWr0IPWDOwthw0p9y3tLgpqelhqERgxA== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1779305969; a=rsa-sha256; cv=none; b=eJU93nZv9QQLKZ1y0JDhAqsXzHUplrPN/gzrhNH2W7upn0jg3LZ+9qC5X/eJWc6ibHiinC WAWLmhzyHQn+Xw0N9ETk/RO3YJM3iS9EuZUoq7BDECacFBSiu0jbqSvV3/ov+ckKtotGaI P+n4VCsg2uFvViuN7u6sHPwT1FrnGhH77JYTSHCnX4l2ZftTHc0inNVdRJqBtPUqLUESLn zcdm1PPDsZZHPcYkUksJa76eBlWgQfC2e5fAxmi9166cA66RaQW55HeN72D2e6h6WrjefL 6n0Ut28Q7BaYXmls2CfB613e8K15O66AUmPh+3Thsh0cPPf6qQubrsUiOZMp0w== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1779305969; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=i7M4Kygpg4aPYIwJGk9VUJnUOZ8nlo33isUD8GJ8ECg=; b=uKaT1aC+O+YYKRqZCCmJLukMojR/OgNz9SksIFcsAyCJMXxOVEBuB4YiWB4UEdwYorYGjq L28TszYZj1x5iGKiV4OXxRZH2Lq/cF/ftb+UYDdhw+HvtBR8yrmy2dayHEy5+dCfhpARqF sDMwPc3fz4BkNexbpzKtXwzWeB7BwqUMnUwMGNQEeQMM2DgJ9YsABNXqi8fnsjIq1XvO5d BGcj9lEWoJA1DYFMJ8RlT7Q19uIOwF3eqA1g4B4f2bj8moPB+7cnv/D5t6yvjKoYY69qFa 7fhZBsQM7nHxbk1ntbSH0aseXbMHC4CAgGVfmx8sethGQEW4R3SgysjjCrWWSQ== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4gLMLT4MZQz1F3j for ; Wed, 20 May 2026 19:39:29 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 37639 by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Wed, 20 May 2026 19:39:29 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-branches@FreeBSD.org From: Mark Johnston Subject: git: c301b76a0054 - releng/15.0 - procdesc: Make sure to drain selinfo sleepers in procdesc_free() List-Id: Commit messages for all branches of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-all List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-all@freebsd.org Sender: owner-dev-commits-src-all@FreeBSD.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: markj X-Git-Repository: src X-Git-Refname: refs/heads/releng/15.0 X-Git-Reftype: branch X-Git-Commit: c301b76a0054bdd81e4156e5bb3a6f5741ffb8fe Auto-Submitted: auto-generated Date: Wed, 20 May 2026 19:39:29 +0000 Message-Id: <6a0e0df1.37639.6eadf6e5@gitrepo.freebsd.org> The branch releng/15.0 has been updated by markj: URL: https://cgit.FreeBSD.org/src/commit/?id=c301b76a0054bdd81e4156e5bb3a6f5741ffb8fe commit c301b76a0054bdd81e4156e5bb3a6f5741ffb8fe Author: Mark Johnston AuthorDate: 2026-05-08 13:03:49 +0000 Commit: Mark Johnston CommitDate: 2026-05-19 23:51:24 +0000 procdesc: Make sure to drain selinfo sleepers in procdesc_free() Otherwise they are left on a freed list after procdesc_free() is called. This can be exploited to elevate privileges. Remove the PDF_SELECTED micro-optimization. doselwakeup() is a no-op if no one ever called selrecord() on the file description, so I see no reason to complicate the code to avoid the call. Add some regression tests. Approved by: so Security: FreeBSD-SA-26:19.file Security: CVE-2026-45251 Reported by: 75Acol, Lexpl0it, fcgboy, and robinzeng2015 Reviewed by: kib, oshogbo Fixes: cfb5f7686588 ("Add experimental support for process descriptors") Differential Revision: https://reviews.freebsd.org/D56887 --- sys/kern/sys_procdesc.c | 10 ++-- sys/sys/procdesc.h | 1 - tests/sys/kern/Makefile | 2 + tests/sys/kern/procdesc.c | 128 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 133 insertions(+), 8 deletions(-) diff --git a/sys/kern/sys_procdesc.c b/sys/kern/sys_procdesc.c index 11bd1b6f30e1..5b03bced4bf3 100644 --- a/sys/kern/sys_procdesc.c +++ b/sys/kern/sys_procdesc.c @@ -270,6 +270,7 @@ procdesc_free(struct procdesc *pd) 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); @@ -312,10 +313,7 @@ procdesc_exit(struct proc *p) 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); @@ -430,10 +428,8 @@ procdesc_poll(struct file *fp, int events, struct ucred *active_cred, 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); } diff --git a/sys/sys/procdesc.h b/sys/sys/procdesc.h index 81102dffa6ff..56d625950a80 100644 --- a/sys/sys/procdesc.h +++ b/sys/sys/procdesc.h @@ -86,7 +86,6 @@ struct procdesc { * 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. */ diff --git a/tests/sys/kern/Makefile b/tests/sys/kern/Makefile index 9044b1e7e4f2..5c2290c67b8f 100644 --- a/tests/sys/kern/Makefile +++ b/tests/sys/kern/Makefile @@ -31,6 +31,7 @@ ATF_TESTS_C+= ktrace_test ATF_TESTS_C+= listener_wakeup ATF_TESTS_C+= module_test ATF_TESTS_C+= prace +ATF_TESTS_C+= procdesc ATF_TESTS_C+= ptrace_test TEST_METADATA.ptrace_test+= timeout="15" ATF_TESTS_C+= reaper @@ -95,6 +96,7 @@ LIBADD.kcov+= pthread CFLAGS.ktls_test+= -DOPENSSL_API_COMPAT=0x10100000L LIBADD.ktls_test+= crypto util LIBADD.listener_wakeup+= pthread +LIBADD.procdesc+= pthread LIBADD.shutdown_dgram+= pthread LIBADD.socket_msg_waitall+= pthread LIBADD.socket_splice+= pthread diff --git a/tests/sys/kern/procdesc.c b/tests/sys/kern/procdesc.c new file mode 100644 index 000000000000..7505e0e4bb94 --- /dev/null +++ b/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()); +}