kqueue timer: Remove detached knotes from the process stop queue

There are some scenarios where a timer event may be detached when it is
on the process' kqueue timer stop queue.  If kqtimer_proc_continue() is
called after that point, it will iterate over the queue and access freed
timer structures.

It is also possible, at least in a multithreaded program, for a stopped
timer event to be scheduled without removing it from the process' stop
queue.  Ensure that we do not doubly enqueue the event structure in this
case.

Reported by:	syzbot+cea0931bb4e34cd728bd@syzkaller.appspotmail.com
Reported by:	syzbot+9e1a2f3734652015998c@syzkaller.appspotmail.com
Reviewed by:	kib
MFC after:	1 week
Sponsored by:	The FreeBSD Foundation
Differential Revision:	https://reviews.freebsd.org/D30251
This commit is contained in:
Mark Johnston 2021-05-14 10:07:56 -04:00
parent 34766aa8cb
commit 2cca77ee01

View File

@ -680,11 +680,14 @@ struct kq_timer_cb_data {
struct proc *p;
struct knote *kn;
int cpuid;
int flags;
TAILQ_ENTRY(kq_timer_cb_data) link;
sbintime_t next; /* next timer event fires at */
sbintime_t to; /* precalculated timer period, 0 for abs */
};
#define KQ_TIMER_CB_ENQUEUED 0x01
static void
kqtimer_sched_callout(struct kq_timer_cb_data *kc)
{
@ -706,6 +709,7 @@ kqtimer_proc_continue(struct proc *p)
TAILQ_FOREACH_SAFE(kc, &p->p_kqtim_stop, link, kc1) {
TAILQ_REMOVE(&p->p_kqtim_stop, kc, link);
kc->flags &= ~KQ_TIMER_CB_ENQUEUED;
if (kc->next <= now)
filt_timerexpire_l(kc->kn, true);
else
@ -753,7 +757,10 @@ filt_timerexpire_l(struct knote *kn, bool proc_locked)
if (!proc_locked)
PROC_LOCK(p);
if (P_SHOULDSTOP(p) || P_KILLED(p)) {
TAILQ_INSERT_TAIL(&p->p_kqtim_stop, kc, link);
if ((kc->flags & KQ_TIMER_CB_ENQUEUED) == 0) {
kc->flags |= KQ_TIMER_CB_ENQUEUED;
TAILQ_INSERT_TAIL(&p->p_kqtim_stop, kc, link);
}
if (!proc_locked)
PROC_UNLOCK(p);
return;
@ -826,6 +833,7 @@ filt_timerattach(struct knote *kn)
kc->kn = kn;
kc->p = curproc;
kc->cpuid = PCPU_GET(cpuid);
kc->flags = 0;
callout_init(&kc->c, 1);
filt_timerstart(kn, to);
@ -856,6 +864,11 @@ filt_timerdetach(struct knote *kn)
kc = kn->kn_ptr.p_v;
callout_drain(&kc->c);
if ((kc->flags & KQ_TIMER_CB_ENQUEUED) != 0) {
PROC_LOCK(kc->p);
TAILQ_REMOVE(&kc->p->p_kqtim_stop, kc, link);
PROC_UNLOCK(kc->p);
}
free(kc, M_KQUEUE);
old = atomic_fetchadd_int(&kq_ncallouts, -1);
KASSERT(old > 0, ("Number of callouts cannot become negative"));