aboutsummaryrefslogtreecommitdiff
path: root/tests/sys/kqueue/kqueue_fork.c
diff options
context:
space:
mode:
authorMark Johnston <markj@FreeBSD.org>2026-07-27 15:28:50 +0000
committerMark Johnston <markj@FreeBSD.org>2026-07-28 15:28:11 +0000
commit5a4222a1b225dc151d4262f1bb6e6ff5c1c0c79a (patch)
tree34548a4c6e6d621fd1aac6ebb17e445d0d470d96 /tests/sys/kqueue/kqueue_fork.c
parent596030c13dce638f3cbaf89b19988f64f677cee5 (diff)
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
Diffstat (limited to 'tests/sys/kqueue/kqueue_fork.c')
-rw-r--r--tests/sys/kqueue/kqueue_fork.c36
1 files changed, 36 insertions, 0 deletions
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());
}