Page MenuHomeFreeBSD

D58223.diff
No OneTemporary

D58223.diff

diff --git a/sys/kern/kern_event.c b/sys/kern/kern_event.c
--- a/sys/kern/kern_event.c
+++ b/sys/kern/kern_event.c
@@ -3075,13 +3075,29 @@
}
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))
@@ -3091,9 +3107,13 @@
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);
@@ -3108,12 +3128,19 @@
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);
}
@@ -3137,7 +3164,7 @@
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
--- a/sys/sys/event.h
+++ b/sys/sys/event.h
@@ -316,6 +316,7 @@
#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
--- a/tests/sys/kqueue/kqueue_fork.c
+++ b/tests/sys/kqueue/kqueue_fork.c
@@ -269,10 +269,46 @@
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());
}

File Metadata

Mime Type
text/plain
Expires
Sat, Aug 8, 5:13 AM (7 h, 49 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
36195162
Default Alt Text
D58223.diff (3 KB)

Event Timeline