Avoid unsynchronized updates to kn_status.

kn_status is protected by the kqueue's lock, but we were updating it
without the kqueue lock held.  For EVFILT_TIMER knotes, there is no
knlist lock, so the knote activation could occur during the kn_status
update and result in KN_QUEUED being lost, in which case we'd enqueue
an already-enqueued knote, corrupting the queue.

Fix the problem by setting or clearing KN_DISABLED before dropping the
kqueue lock to call into the filter.  KN_DISABLED is used only by the
core kevent code, so there is no side effect from setting it earlier.

Reported and tested by:	Sylvain GALLIANO <sg@efficientip.com>
Reviewed by:	kib
MFC after:	3 days
Sponsored by:	The FreeBSD Foundation
Differential Revision:	https://reviews.freebsd.org/D18060
This commit is contained in:
markj 2018-11-21 17:32:09 +00:00
parent 6994e92830
commit 2e57d41f44

View File

@ -1533,6 +1533,8 @@ findkn:
kn->kn_kevent.flags &= ~(EV_ADD | EV_DELETE |
EV_ENABLE | EV_DISABLE | EV_FORCEONESHOT);
kn->kn_status = KN_DETACHED;
if ((kev->flags & EV_DISABLE) != 0)
kn->kn_status |= KN_DISABLED;
kn_enter_flux(kn);
error = knote_attach(kn, kq);
@ -1568,6 +1570,11 @@ findkn:
KNOTE_ACTIVATE(kn, 1);
}
if ((kev->flags & EV_ENABLE) != 0)
kn->kn_status &= ~KN_DISABLED;
else if ((kev->flags & EV_DISABLE) != 0)
kn->kn_status |= KN_DISABLED;
/*
* The user may change some filter values after the initial EV_ADD,
* but doing so will not reset any filter which has already been
@ -1585,19 +1592,17 @@ findkn:
kn->kn_sdata = kev->data;
}
done_ev_add:
/*
* We can get here with kn->kn_knlist == NULL. This can happen when
* the initial attach event decides that the event is "completed"
* already. i.e. filt_procattach is called on a zombie process. It
* will call filt_proc which will remove it from the list, and NULL
* already, e.g., filt_procattach() is called on a zombie process. It
* will call filt_proc() which will remove it from the list, and NULL
* kn_knlist.
*
* KN_DISABLED will be stable while the knote is in flux, so the
* unlocked read will not race with an update.
*/
done_ev_add:
if ((kev->flags & EV_ENABLE) != 0)
kn->kn_status &= ~KN_DISABLED;
else if ((kev->flags & EV_DISABLE) != 0)
kn->kn_status |= KN_DISABLED;
if ((kn->kn_status & KN_DISABLED) == 0)
event = kn->kn_fop->f_event(kn, 0);
else