diff --git a/sys/kern/kern_jail.c b/sys/kern/kern_jail.c --- a/sys/kern/kern_jail.c +++ b/sys/kern/kern_jail.c @@ -3013,13 +3013,6 @@ prison_deref(pr, drflags); } -static void -prison_kill_processes_cb(struct proc *p, void *arg __unused) -{ - - kern_psignal(p, SIGKILL); -} - /* * Note the iteration does not guarantee acting on all processes. * Most notably there may be fork or jail_attach in progress. @@ -3191,7 +3184,7 @@ /* Kill any processes attached to a killed prison. */ if (killpr != NULL) - prison_proc_iterate(killpr, prison_kill_processes_cb, NULL); + kern_prsignal(curthread, killpr, SIGKILL); /* * Finish removing any unreferenced prisons, which couldn't happen diff --git a/sys/kern/kern_sig.c b/sys/kern/kern_sig.c --- a/sys/kern/kern_sig.c +++ b/sys/kern/kern_sig.c @@ -43,6 +43,7 @@ #include "opt_ktrace.h" #include +#include #include #include #include @@ -83,6 +84,7 @@ #include #include #include +#include #include #include #include @@ -108,7 +110,7 @@ "struct thread *", "struct proc *", "int"); static int coredump(struct thread *); -static int killpg1(struct thread *td, int sig, int pgid, int all, +static int killpg1(struct thread *td, int sig, int pgid, struct prison *pr, ksiginfo_t *ksi); static int issignal(struct thread *td); static void reschedule_signals(struct proc *p, sigset_t block, int flags); @@ -1765,27 +1767,105 @@ struct killpg1_ctx { struct thread *td; + struct proc *target; + struct ucred *cr; + struct sx *lock; ksiginfo_t *ksi; int sig; bool sent; bool found; + int count; int ret; + struct unrhdr pids; + struct task t; }; static void -killpg1_sendsig_locked(struct proc *p, struct killpg1_ctx *arg) +killpg1_kill_proc_locked(struct killpg1_ctx *arg) { + struct proc *p; + bool need_stop; int err; - err = p_cansignal(arg->td, p, arg->sig); - if (err == 0 && arg->sig != 0) - pksignal(p, arg->sig, arg->ksi); - if (err != ESRCH) + p = arg->target; + PROC_LOCK_ASSERT(p, MA_OWNED); + PROC_ASSERT_HELD(p); + + err = cr_cansignal(arg->cr, p, arg->sig); + if (err != ESRCH) { arg->found = true; + arg->count++; + } if (err == 0) arg->sent = true; else if (arg->ret == 0 && err != ESRCH && err != EPERM) arg->ret = err; + if (err != 0 || arg->sig == 0) + return; + + /* + * The need_stop indicates if the target process needs to be + * suspended before being signalled. This is needed when we + * guarantee that all processes in subtree are signalled, + * avoiding the race with some process not yet fully linked + * into all structures during fork, ignored by iterator, and + * then escaping signalling. + * + * The thread cannot usefully stop itself anyway, and if other + * thread of the current process forks while the current + * thread signals the whole subtree, it is an application + * race. + */ + + if ((p->p_flag & (P_KPROC | P_SYSTEM | P_STOPPED)) == 0) + need_stop = thread_single(p, SINGLE_ALLPROC) == 0; + else + need_stop = false; + (void)pksignal(p, arg->sig, arg->ksi); + if (need_stop) + thread_single_end(p, SINGLE_ALLPROC); +} + + +static void +killpg1_proc_work(void *arg, int pending __unused) +{ + struct killpg1_ctx *w; + + w = arg; + PROC_LOCK(w->target); + if ((w->target->p_flag2 & P2_WEXIT) == 0) + killpg1_kill_proc_locked(w); + PROC_UNLOCK(w->target); + + sx_xlock(w->lock); + w->target = NULL; + wakeup(&w->target); + sx_xunlock(w->lock); +} + + +static void +killpg1_sendsig_locked(struct proc *p, struct killpg1_ctx *arg, struct sx *lock) +{ + + sx_assert(lock, SX_SLOCKED); + + if ((p->p_flag2 & P2_WEXIT) != 0) + return; + _PHOLD_LITE(p); + PROC_UNLOCK(p); + if (alloc_unr_specific(&arg->pids, p->p_pid) != p->p_pid) + goto out; + arg->target = p; + arg->lock = lock; + taskqueue_enqueue(taskqueue_thread, &arg->t); + while (arg->target != NULL) { + sx_sleep(&arg->target, lock, PWAIT, "killpg", 0); + } +out: + PROC_LOCK(p); + _PRELE(p); } static void @@ -1797,7 +1877,7 @@ return; PROC_LOCK(p); - killpg1_sendsig_locked(p, arg); + killpg1_sendsig_locked(p, arg, &proctree_lock); PROC_UNLOCK(p); } @@ -1810,7 +1890,7 @@ (p == ctx->td->td_proc) || p->p_state == PRS_NEW) return; - killpg1_sendsig_locked(p, ctx); + killpg1_sendsig_locked(p, ctx, &allproc_lock); } /* @@ -1818,51 +1898,86 @@ * cp is calling process. */ static int -killpg1(struct thread *td, int sig, int pgid, int all, ksiginfo_t *ksi) +killpg1(struct thread *td, int sig, int pgid, struct prison *pr, + ksiginfo_t *ksi) { - struct proc *p; + struct proc *p, *ptmp; struct pgrp *pgrp; struct killpg1_ctx arg; + arg.cr = crhold(td->td_ucred); arg.td = td; arg.ksi = ksi; arg.sig = sig; arg.sent = false; - arg.found = false; + arg.count = 0; arg.ret = 0; - if (all) { - /* - * broadcast - */ - prison_proc_iterate(td->td_ucred->cr_prison, - kill_processes_prison_cb, &arg); - } else { - sx_slock(&proctree_lock); - if (pgid == 0) { + init_unrhdr(&arg.pids, 1, PID_MAX, UNR_NO_MTX); + TASK_INIT(&arg.t, 0, killpg1_proc_work, &arg); + + /* + * Prevent swapout, since ctx, ksi are + * allocated on the stack. We sleep in + * killpg1_sendsig_locked() waiting for + * task to complete single-threading. + */ + PHOLD(td->td_proc); + + do { + arg.found = false; + if (pr != NULL) { /* - * zero pgid means send to my process group. + * broadcast */ - pgrp = td->td_proc->p_pgrp; - PGRP_LOCK(pgrp); + prison_proc_iterate(pr, kill_processes_prison_cb, &arg); } else { - pgrp = pgfind(pgid); - if (pgrp == NULL) { - sx_sunlock(&proctree_lock); - return (ESRCH); + sx_slock(&proctree_lock); + if (pgid == 0) { + /* + * zero pgid means send to my process group. + */ + pgrp = td->td_proc->p_pgrp; + PGRP_LOCK(pgrp); + } else { + pgrp = pgfind(pgid); + if (pgrp == NULL) { + sx_sunlock(&proctree_lock); + arg.ret = ESRCH; + goto out; + } } + PGRP_UNLOCK(pgrp); + LIST_FOREACH_SAFE(p, &pgrp->pg_members, p_pglist, ptmp) + killpg1_sendsig(p, false, &arg); + sx_sunlock(&proctree_lock); } - sx_sunlock(&proctree_lock); - LIST_FOREACH(p, &pgrp->pg_members, p_pglist) { - killpg1_sendsig(p, false, &arg); - } - PGRP_UNLOCK(pgrp); - } - MPASS(arg.ret != 0 || arg.found || !arg.sent); + } while (arg.found); +out: + PRELE(td->td_proc); + crfree(arg.cr); + clean_unrhdr(&arg.pids); + clear_unrhdr(&arg.pids); + + MPASS(arg.ret != 0 || arg.count != 0 || !arg.sent); if (arg.ret == 0 && !arg.sent) - arg.ret = arg.found ? EPERM : ESRCH; + arg.ret = arg.count != 0 ? EPERM : ESRCH; return (arg.ret); } +int +kern_prsignal(struct thread *td, struct prison *pr, int sig) +{ + ksiginfo_t ksi; + + ksiginfo_init(&ksi); + ksi.ksi_signo = sig; + ksi.ksi_code = SI_USER; + ksi.ksi_pid = td->td_proc->p_pid; + ksi.ksi_uid = td->td_ucred->cr_ruid; + + return (killpg1(td, sig, 0, pr, &ksi)); +} + #ifndef _SYS_SYSPROTO_H_ struct kill_args { int pid; @@ -1916,11 +2031,11 @@ } switch (pid) { case -1: /* broadcast signal */ - return (killpg1(td, signum, 0, 1, &ksi)); + return (killpg1(td, signum, 0, td->td_ucred->cr_prison, &ksi)); case 0: /* signal own process group */ - return (killpg1(td, signum, 0, 0, &ksi)); + return (killpg1(td, signum, 0, NULL, &ksi)); default: /* negative explicit process group */ - return (killpg1(td, signum, -pid, 0, &ksi)); + return (killpg1(td, signum, -pid, NULL, &ksi)); } /* NOTREACHED */ } diff --git a/sys/sys/signalvar.h b/sys/sys/signalvar.h --- a/sys/sys/signalvar.h +++ b/sys/sys/signalvar.h @@ -324,6 +324,7 @@ } struct pgrp; +struct prison; struct proc; struct sigio; struct thread; @@ -390,6 +391,7 @@ void pgsigio(struct sigio **sigiop, int sig, int checkctty); void pgsignal(struct pgrp *pgrp, int sig, int checkctty, ksiginfo_t *ksi); int postsig(int sig); +int kern_prsignal(struct thread *td, struct prison *pr, int sig); void kern_psignal(struct proc *p, int sig); int ptracestop(struct thread *td, int sig, ksiginfo_t *si); void sendsig(sig_t catcher, ksiginfo_t *ksi, sigset_t *retmask);