DF-2691 / fix.diff
diff --git a/sys/kern/kern_sig.c b/sys/kern/kern_sig.c --- a/sys/kern/kern_sig.c 2026-08-30 21:40:34.351491983 +0000 +++ b/sys/kern/kern_sig.c 2026-08-30 21:40:34.371491727 +0000 @@ -2681,8 +2681,15 @@ static void filt_sigdetach(struct knote *kn) { - struct proc *p = kn->kn_ptr.p_proc; + struct proc *p; + /* + * The target process may already be gone (it detached us in + * filt_signal() on NOTE_EXIT); do not touch it in that case. + */ + if (kn->kn_status & KN_DETACHED) + return; + p = kn->kn_ptr.p_proc; knote_remove(&p->p_klist, kn); } @@ -2701,5 +2708,23 @@ if (kn->kn_id == hint) kn->kn_data++; } + /* + * Signal knotes share p->p_klist with EVFILT_PROC knotes. When + * the target process exits, exit1() broadcasts NOTE_EXIT to the + * list; we used to ignore it, which left this knote linked into + * p->p_klist past the reaping kfree(p, M_PROC) -- a use-after-free + * once the kqueue is destroyed or the knote is deleted. Detach on + * NOTE_EXIT exactly like filt_proc() does. + */ + if ((hint & NOTE_PCTRLMASK) == NOTE_EXIT && + (kn->kn_status & KN_DETACHED) == 0) { + struct proc *p = kn->kn_ptr.p_proc; + + PHOLD(p); + knote_remove(&p->p_klist, kn); + kn->kn_status |= KN_DETACHED; + kn->kn_ptr.p_proc = NULL; + PRELE(p); + } return (kn->kn_data != 0); } |