From nobody Wed Jul 29 17:48:38 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 4h9KZG5qGpz6mZkp for ; Wed, 29 Jul 2026 17:48:38 +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 "YR1" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id 4h9KZG4bF4z3hJ1 for ; Wed, 29 Jul 2026 17:48:38 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1785347318; 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=S7l0OrhHkfs29kRAB6aDVHEBvTLVHzZ8mcoP9KhIVDk=; b=os6U1xuNB+0lV7w1LTOWjRNhd0oVoEdyvvfT3suE3XCHvPjrOFStL4udD43qTAreireWQy CkgcMeiy+C6H9UeVRyauW8xvM+3FF91D47Wodg7NOQy01QCL26cTB5PpGKZJrfUw3ARxC5 8set54CAb+3T46yfmn1Xejaki3XnS/i8Jg9EXyJzfFelJ4gC3ewYKuai+eGVKkk4Je5bJF yAGxIo+GguQNhZjNre+682wy8TLbIhi2SJ1IK56Q3NvErAbo5Kk4G6pp9I5i8zdJ4dZJmL X1NRlgc5fZLmnSVRaktwzebLliKMjoY1mkOyOKHb/ocvac3X1kdznd7M8Vu6rQ== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1785347318; a=rsa-sha256; cv=none; b=R4ROHAb5toNM8SuwwBuQ30EFf5w0gONGG+UqMysT8fsvpguYmwwrHyXbkrbboKq/CvnAcz bElyxgJP7qCPRrpnaKVHB67vrOEEevkTkV/OqcZeMy3EGGemBi7kDPXweYKoaB3hU3X9Km Bz+fP6hjKbyNTBHBf4oXmtFSnOIQKVZc7JGBeI0oPWyJNOw8gxASGDGlweEOfGH4+ea+0M ATzRmvdXaJqxb/NNV7AhcduJF99kvw1vwyfSnozsPqMuEG6enYHdcs9RXZXpt/h56ccnRu Q7T7IyGgJr/AajZ+rpHD1tPDaPtk3Xwqum+6p12aTZ6JTUN4/YUELsWoPtq6YA== 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=1785347318; 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=S7l0OrhHkfs29kRAB6aDVHEBvTLVHzZ8mcoP9KhIVDk=; b=gHaIe4avAdTNIUasjaBHcX/UCVD2fSmtEC1CHEE2tc6xENJZjYg2TUUccQuVqIFks2ST1G IFNsTw/BcJl8X6JU/9rXNzWov9D1hhY0JTsrD2vbcGSX/PC3QcHE0pWlXU5VH97R8KLOuK rip3qoy7Y+6msJ87s/87PohHF1LPypfS9zo2VzcGupqI3GfG36ILApuuxN/kA7sOF29V61 xpMf5PbiY1gYhSkkd7J7CrNO7w38DktraD+eQvo7NsBxtR6vmk3vaZJV8Uwk0agSsRYCb2 lE1s6Lt8Mw1nGuMkNT4Gxmxfd9KWoLDUexBVsVKoAX5gHhkMbPUwoc1pmio9Ww== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4h9KZG3fgmzdPD for ; Wed, 29 Jul 2026 17:48:38 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 3ad13 by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Wed, 29 Jul 2026 17:48:38 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-branches@FreeBSD.org From: Mark Johnston Subject: git: cb7cb40ae47b - stable/15 - kqueue: Avoid enqueuing an already-enqueued knote 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/stable/15 X-Git-Reftype: branch X-Git-Commit: cb7cb40ae47b3d62317f3a554e9a491d4a105eb4 Auto-Submitted: auto-generated Date: Wed, 29 Jul 2026 17:48:38 +0000 Message-Id: <6a6a3cf6.3ad13.21e0fd22@gitrepo.freebsd.org> The branch stable/15 has been updated by markj: URL: https://cgit.FreeBSD.org/src/commit/?id=cb7cb40ae47b3d62317f3a554e9a491d4a105eb4 commit cb7cb40ae47b3d62317f3a554e9a491d4a105eb4 Author: Mark Johnston AuthorDate: 2026-07-27 15:28:50 +0000 Commit: Mark Johnston CommitDate: 2026-07-29 17:48:13 +0000 kqueue: Avoid enqueuing an already-enqueued knote knotes with a non-trivial f_copy implementation may be activated before kqueue_fork_copy_knote() is finished. In particular, it may be enqueued at the time that kqueue_fork_copy_knote() calls knote_enqueue(). Guard against this. Add a test case which triggers the race. Fix several other problems with the replication of knote state: - Make sure only the KN_ACTIVE and KN_DISABLED status flags are inherited, the rest should not be copied. - Ignore marker knotes. - Ignore knotes for kqueues. They cannot be safely copied into the child without more work, as kqueues are inherently local to a process; on fork, we need to ensure that such knotes are patched to reference the new kqueue, not the original. - Try to keep knote state stable by holding the kqueue and knlist locks while copying. Approved by: so Security: FreeBSD-SA-26:50.kqueue Security: CVE-2026-58083 Reviewed by: kib Reported by: Hazley Samsudin of GovTech CSG Sponsored by: The FreeBSD Foundation Differential Revision: https://reviews.freebsd.org/D58223 --- sys/kern/kern_event.c | 43 ++++++++++++++++++++++++++++++++++-------- sys/sys/event.h | 1 + tests/sys/kqueue/kqueue_fork.c | 36 +++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 8 deletions(-) diff --git a/sys/kern/kern_event.c b/sys/kern/kern_event.c index 01bd22fbefd1..706f0e6d658e 100644 --- a/sys/kern/kern_event.c +++ b/sys/kern/kern_event.c @@ -3072,13 +3072,29 @@ kqueue_fork_alloc(struct filedesc *fdp, struct file *fp, struct file **fp1, } static void -kqueue_fork_copy_knote(struct kqueue *kq1, struct knote *kn, struct proc *p1, - struct filedesc *fdp) +kqueue_fork_copy_knote(struct kqueue *kq, struct kqueue *kq1, struct knote *kn, + struct proc *p1, struct filedesc *fdp) { struct knote *kn1; + struct knlist *knl; const struct filterops *fop; int error; + bool enqueue; + KASSERT(kn->kn_influx != 0, + ("%s: knote %p not in flux", __func__, kn)); + KASSERT((kn->kn_status & KN_DETACHED) == 0, + ("%s: knote %p not detached", __func__, kn)); + + if ((kn->kn_status & KN_MARKER) != 0) + return; + if ((kn->kn_status & KN_KQUEUE) != 0) { + /* + * We cannot hold references to a kqueue outside of the process + * itself, kqueue_close() does not handle this possibility. + */ + return; + } fop = kn->kn_fop; if (fop->f_copy == NULL || (fop->f_isfd && fdp->fd_files->fdt_ofiles[kn->kn_kevent.ident].fde_file == NULL)) @@ -3088,9 +3104,13 @@ kqueue_fork_copy_knote(struct kqueue *kq1, struct knote *kn, struct proc *p1, return; kn1 = knote_alloc(M_WAITOK); + + knl = kn_list_lock(kn); + KQ_LOCK(kq); *kn1 = *kn; - kn1->kn_status |= KN_DETACHED; - kn1->kn_status &= ~KN_QUEUED; + KQ_UNLOCK(kq); + kn_list_unlock(knl); + kn1->kn_status = KN_DETACHED | (kn1->kn_status & KN_CPONFORK); kn1->kn_kq = kq1; kn1->kn_knlist = NULL; error = fop->f_copy(kn1, p1); @@ -3105,12 +3125,19 @@ kqueue_fork_copy_knote(struct kqueue *kq1, struct knote *kn, struct proc *p1, knote_free(kn1); return; } - if (kn->kn_knlist != NULL) - knlist_add(kn->kn_knlist, kn1, 0); + if (kn->kn_knlist != NULL) { + knl = kn_list_lock(kn); + knlist_add(kn->kn_knlist, kn1, 1); + } else { + knl = NULL; + } + enqueue = kn->kn_fop->f_event(kn1, 0) != 0; + kn_list_unlock(knl); + KQ_LOCK(kq1); knote_attach(kn1, kq1); kn1->kn_influx = 0; - if ((kn->kn_status & KN_QUEUED) != 0) + if (enqueue && (kn1->kn_status & KN_QUEUED) == 0) knote_enqueue(kn1); KQ_UNLOCK(kq1); } @@ -3134,7 +3161,7 @@ kqueue_fork_copy_list(struct klist *knlist, struct knote *marker, kn_enter_flux(kn); SLIST_INSERT_AFTER(kn, marker, kn_link); KQ_UNLOCK(kq); - kqueue_fork_copy_knote(kq1, kn, p1, fdp); + kqueue_fork_copy_knote(kq, kq1, kn, p1, fdp); KQ_LOCK(kq); kn_leave_flux(kn); kn = SLIST_NEXT(marker, kn_link); diff --git a/sys/sys/event.h b/sys/sys/event.h index de24c9226e4a..a863cd20f45d 100644 --- a/sys/sys/event.h +++ b/sys/sys/event.h @@ -315,6 +315,7 @@ struct knote { #define KN_MARKER 0x20 /* ignore this knote */ #define KN_KQUEUE 0x40 /* this knote belongs to a kq */ #define KN_SCAN 0x100 /* flux set in kqueue_scan() */ +#define KN_CPONFORK (KN_ACTIVE | KN_DISABLED) /* state preserved by fork */ int kn_influx; unsigned int kn_sfflags; /* saved filter flags */ int64_t kn_sdata; /* saved data field */ diff --git a/tests/sys/kqueue/kqueue_fork.c b/tests/sys/kqueue/kqueue_fork.c index ad8f69056e07..03b831e4f77c 100644 --- a/tests/sys/kqueue/kqueue_fork.c +++ b/tests/sys/kqueue/kqueue_fork.c @@ -269,10 +269,46 @@ ATF_TC_BODY(cponfork_notes, tc) cponfork_notes_mask_check(info.si_status, true); } +/* + * Exercise a rare race: while the kernel is copying knotes during a fork, try + * to set things up so that a new knote is activated while the copy is still in + * progress. + */ +ATF_TC_WITHOUT_HEAD(cponfork_timer_race); +ATF_TC_BODY(cponfork_timer_race, tc) +{ + struct kevent ev; + int error, kq, status; + pid_t pid; + + for (int i = 0; i < 100; i++) { + kq = kqueuex(KQUEUE_CPONFORK); + ATF_REQUIRE(kq >= 0); + + EV_SET(&ev, 0, EVFILT_TIMER, EV_ADD | EV_ENABLE, NOTE_NSECONDS, + 1, NULL); + error = kevent(kq, &ev, 1, NULL, 0, NULL); + ATF_REQUIRE(error == 0); + + pid = fork(); + ATF_REQUIRE(pid != -1); + if (pid == 0) + _exit(0); + + error = waitpid(pid, &status, 0); + ATF_REQUIRE(error != -1); + ATF_REQUIRE(WIFEXITED(status)); + ATF_REQUIRE_EQ(WEXITSTATUS(status), 0); + + ATF_REQUIRE(close(kq) == 0); + } +} + ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, shared_table_filt_sig); ATF_TP_ADD_TC(tp, cponfork_notes); + ATF_TP_ADD_TC(tp, cponfork_timer_race); return (atf_no_error()); }