diff --git a/kern/kern_jail.c b/kern/kern_jail.c --- a/kern/kern_jail.c +++ b/kern/kern_jail.c @@ -144,7 +144,6 @@ static void prison_deref_kill(struct prison *pr, struct prisonlist *freeprison); static int prison_lock_xlock(struct prison *pr, int flags); static void prison_free_not_last(struct prison *pr); -static void prison_proc_free_not_last(struct prison *pr); static void prison_set_allow_locked(struct prison *pr, unsigned flag, int enable); static char *prison_path(struct prison *pr1, struct prison *pr2); @@ -1000,6 +999,11 @@ } sx_xlock(&allprison_lock); drflags = PD_LIST_XLOCKED; + if (!prison_isalive(mypr)) { + /* This jail is dying. This process will surely follow. */ + error = EAGAIN; + goto done_deref; + } if (jid != 0) { if (jid < 0) { error = EINVAL; @@ -1111,8 +1115,7 @@ "jail \"%s\" not found", name); goto done_deref; } - if (!(flags & JAIL_DYING) && - !prison_isalive(ppr)) { + if (!prison_isalive(ppr)) { mtx_unlock(&ppr->pr_mtx); error = ENOENT; vfs_opterror(opts, @@ -1202,19 +1205,7 @@ goto done_deref; } prison_hold(ppr); - if (!refcount_acquire_if_not_zero(&ppr->pr_uref)) { - /* This brings the parent back to life. */ - mtx_lock(&ppr->pr_mtx); - refcount_acquire(&ppr->pr_uref); - ppr->pr_state = PRISON_STATE_ALIVE; - mtx_unlock(&ppr->pr_mtx); - error = osd_jail_call(ppr, PR_METHOD_CREATE, opts); - if (error) { - pr = ppr; - drflags |= PD_DEREF | PD_DEUREF; - goto done_deref; - } - } + prison_proc_hold(ppr); if (jid == 0 && (jid = get_next_prid(&inspr)) == 0) { error = EAGAIN; @@ -2416,6 +2407,18 @@ #endif prison_deref(oldcred->cr_prison, drflags); crfree(oldcred); + + /* + * If the prison was killed while changing credentials, die along + * with it. + */ + if (!prison_isalive(pr)) { + /* Follow the prison into death. */ + PROC_LOCK(p); + kern_psignal(p, SIGKILL); + PROC_UNLOCK(p); + } + return (0); e_unlock: @@ -2606,7 +2609,8 @@ * user-visible, except through the the jail system calls. It is also * an error to hold an invalid prison. A prison record will remain * alive as long as it has at least one user reference, and will not - * be set to the dying state was long as the prison mutex is held. + * be set to the dying state was long as either the prison mutex or + * the allprison lock is held (allprison_lock may be shared). */ void prison_proc_hold(struct prison *pr) @@ -2655,24 +2659,6 @@ } } -static void -prison_proc_free_not_last(struct prison *pr) -{ -#ifdef INVARIANTS - int lastref; - - KASSERT(refcount_load(&pr->pr_uref) > 0, - ("Trying to free dead prison %p (jid=%d).", - pr, pr->pr_id)); - lastref = refcount_release(&pr->pr_uref); - KASSERT(!lastref, - ("prison_free_not_last freed last ref on prison %p (jid=%d).", - pr, pr->pr_id)); -#else - refcount_release(&pr>pr_uref); -#endif -} - /* * Complete a call to either prison_free or prison_proc_free. */ @@ -2720,25 +2706,13 @@ for (;;) { if (flags & PD_KILL) { /* Kill the prison and its descendents. */ - flags &= ~PD_KILL; if (!(flags & PD_DEREF)) { prison_hold(pr); flags |= PD_DEREF; } flags = prison_lock_xlock(pr, flags); - if (pr->pr_state != PRISON_STATE_DYING) { - /* - * The prison might currently be invalid, - * but call it alive now so PD_DEUREF will - * notice it later. - */ - pr->pr_state = PRISON_STATE_ALIVE; - if (!(flags & PD_DEUREF)) { - refcount_acquire(&pr->pr_uref); - flags |= PD_DEUREF; - } + if (pr->pr_state != PRISON_STATE_DYING) prison_deref_kill(pr, &freeprison); - } } if (flags & PD_DEUREF) { /* Drop a user reference. */ @@ -2894,37 +2868,40 @@ bool descend; /* - * The operation each descendant is similar to what prison_deref() - * does when losing the last references, plus clearing PR_PERSIST. + * The operation for the prison and each descendant is similar to + * what prison_deref() does when losing the last references, plus + * clearing PR_PERSIST. */ + pr->pr_state = PRISON_STATE_DYING; mtx_unlock(&pr->pr_mtx); FOREACH_PRISON_DESCENDANT_PRE_POST(pr, cpr, descend) { - if (!prison_isalive(cpr)) - continue; if (descend) { + if (!prison_isalive(cpr)) { + descend = false; + continue; + } prison_hold(cpr); - prison_proc_hold(cpr); + mtx_lock(&cpr->pr_mtx); + cpr->pr_state = PRISON_STATE_DYING; + cpr->pr_flags |= PR_REMOVE; + mtx_unlock(&cpr->pr_mtx); continue; } + if (!(cpr->pr_flags & PR_REMOVE)) + continue; + (void)osd_jail_call(cpr, PR_METHOD_REMOVE, NULL); mtx_lock(&cpr->pr_mtx); + cpr->pr_flags &= ~PR_REMOVE; if (cpr->pr_flags & PR_PERSIST) { cpr->pr_flags &= ~PR_PERSIST; - prison_proc_free_not_last(cpr); - prison_free_not_last(cpr); + (void)refcount_release(&pr->pr_uref); } - if (refcount_release(&cpr->pr_uref)) { - cpr->pr_state = PRISON_STATE_DYING; - mtx_unlock(&cpr->pr_mtx); - (void)osd_jail_call(cpr, PR_METHOD_REMOVE, NULL); - mtx_lock(&cpr->pr_mtx); - } if (refcount_release(&cpr->pr_ref)) { cpr->pr_state = PRISON_STATE_INVALID; TAILQ_REMOVE(&allprison, cpr, pr_list); TAILQ_INSERT_TAIL(freeprison, cpr, pr_list); mtx_unlock(&cpr->pr_mtx); ppr = cpr->pr_parent; - prison_proc_free_not_last(ppr); prison_free_not_last(ppr); for (; ppr != NULL; ppr = ppr->pr_parent) ppr->pr_childcount--; @@ -2933,12 +2910,14 @@ mtx_unlock(&cpr->pr_mtx); } + (void)osd_jail_call(cpr, PR_METHOD_REMOVE, NULL); mtx_lock(&pr->pr_mtx); if (pr->pr_flags & PR_PERSIST) { pr->pr_flags &= ~PR_PERSIST; - prison_proc_free_not_last(pr); + (void)refcount_release(&pr->pr_uref); prison_free_not_last(pr); } + mtx_unlock(&cpr->pr_mtx); /* * Disconnect unreferenced descendants from their parents, @@ -3146,8 +3125,7 @@ } /* - * Return true if the prison is currently alive. A prison is alive if it - * holds user references. + * Return true if the prison is currently alive. */ bool prison_isalive(struct prison *pr) diff --git a/sys/jail.h b/sys/jail.h --- a/sys/jail.h +++ b/sys/jail.h @@ -221,6 +221,7 @@ /* primary jail address. */ /* Internal flag bits */ +#define PR_REMOVE 0x01000000 /* In process of being removed */ #define PR_IP4 0x02000000 /* IPv4 restricted or disabled */ /* by this jail or an ancestor */ #define PR_IP6 0x04000000 /* IPv6 restricted or disabled */ diff --git a/sys/kern/kern_fork.c b/sys/kern/kern_fork.c --- a/sys/kern/kern_fork.c +++ b/sys/kern/kern_fork.c @@ -688,6 +688,13 @@ p2->p_state = PRS_NORMAL; PROC_SUNLOCK(p2); + /* + * If the prison was killed while the process was new, die along + * with it. + */ + if (!prison_isalive(p2->p_ucred->cr_prison)) + kern_psignal(p2, SIGKILL); + #ifdef KDTRACE_HOOKS /* * Tell the DTrace fasttrap provider about the new process so that any