The system revokes open files for a tty device when the device is being reused. The revoking has applied to the old poll/select implementations sort of automagically because the scan code has always (re)walked the path from file to vnode to device. With kqueue, the rewalking does not happen in particular when event registrations are preserved between system calls.
Ideally, the revoking of a tty device should invalidate any associated knotes. The invalidation could be done in the device close routine. However, the revoking takes full effect only after vclean() has changed the vnode v_op pointer to dead_vops. The kevent() system call is already unlocked, so programs can in principle register new knotes until the pointer update becomes visible to other threads. To avoid going down the rabbit hole of vnode revoking right now, the diff below adds just-in-time fixing of knotes that should be cleared. It stops programs from keeping their knotes on revoked ttys. It is ugly but does not litter the already complex tty code with kqueue-specific stopgaps. OK? Index: kern/kern_event.c =================================================================== RCS file: src/sys/kern/kern_event.c,v retrieving revision 1.177 diff -u -p -r1.177 kern_event.c --- kern/kern_event.c 20 Dec 2021 16:24:32 -0000 1.177 +++ kern/kern_event.c 23 Dec 2021 14:20:54 -0000 @@ -55,6 +55,7 @@ #include <sys/syscallargs.h> #include <sys/time.h> #include <sys/timeout.h> +#include <sys/vnode.h> #include <sys/wait.h> #ifdef DIAGNOSTIC @@ -1381,6 +1396,31 @@ retry: continue; } + /* Fix knotes whose vnodes have been revoked. */ + if ((kn->kn_fop->f_flags & FILTEROP_ISFD) && + kn->kn_fp != NULL && + kn->kn_fp->f_type == DTYPE_VNODE) { + struct vnode *vp = kn->kn_fp->f_data; + + if (__predict_false(vp->v_op == &dead_vops && + kn->kn_fop != &dead_filtops)) { + filter_detach(kn); + kn->kn_fop = &dead_filtops; + + /* + * Check if the event should be delivered. + * Use f_event directly because this is + * a special situation. + */ + if (kn->kn_fop->f_event(kn, 0) == 0) { + filter_detach(kn); + knote_drop(kn, p); + mtx_enter(&kq->kq_lock); + continue; + } + } + } + memset(kevp, 0, sizeof(*kevp)); if (filter_process(kn, kevp) == 0) { mtx_enter(&kq->kq_lock);