kevent: Fix races between timer detach and kqtimer_proc_continue()

- When detaching a knote, we need to double check the enqueued flag
  after acquiring the process lock, as kqtimer_proc_continue() may have
  toggled it.
- kqtimer_proc_continue() could in principle reschedule a stopped
  callout after filt_timerdetach() drains the callout.  So, we need to
  re-check.

Reported by:	syzbot+4a4cebb3ec07892cb040@syzkaller.appspotmail.com
Reported by:	syzbot+a9c04bc76078a3b7dd8d@syzkaller.appspotmail.com
Reviewed by:	kib
MFC after:	1 week
Sponsored by:	The FreeBSD Foundation
Differential Revision:	https://reviews.freebsd.org/D31772
This commit is contained in:
Mark Johnston 2021-09-01 14:18:58 -04:00
parent d491b42535
commit c511383de7

View File

@ -859,14 +859,24 @@ filt_timerdetach(struct knote *kn)
{
struct kq_timer_cb_data *kc;
unsigned int old __unused;
bool pending;
kc = kn->kn_ptr.p_v;
callout_drain(&kc->c);
if ((kc->flags & KQ_TIMER_CB_ENQUEUED) != 0) {
do {
callout_drain(&kc->c);
/*
* kqtimer_proc_continue() might have rescheduled this callout.
* Double-check, using the process mutex as an interlock.
*/
PROC_LOCK(kc->p);
TAILQ_REMOVE(&kc->p->p_kqtim_stop, kc, link);
if ((kc->flags & KQ_TIMER_CB_ENQUEUED) != 0) {
kc->flags &= ~KQ_TIMER_CB_ENQUEUED;
TAILQ_REMOVE(&kc->p->p_kqtim_stop, kc, link);
}
pending = callout_pending(&kc->c);
PROC_UNLOCK(kc->p);
}
} while (pending);
free(kc, M_KQUEUE);
old = atomic_fetchadd_int(&kq_ncallouts, -1);
KASSERT(old > 0, ("Number of callouts cannot become negative"));