diff options
| author | Mark Johnston <markj@FreeBSD.org> | 2026-07-27 15:28:50 +0000 |
|---|---|---|
| committer | Mark Johnston <markj@FreeBSD.org> | 2026-07-28 15:28:11 +0000 |
| commit | 5a4222a1b225dc151d4262f1bb6e6ff5c1c0c79a (patch) | |
| tree | 34548a4c6e6d621fd1aac6ebb17e445d0d470d96 /tests/sys/kqueue/kqueue_fork.c | |
| parent | 596030c13dce638f3cbaf89b19988f64f677cee5 (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.c | 36 |
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()); } |
