Changeset View
Changeset View
Standalone View
Standalone View
kern/kern_jail_28515.c
| Show First 20 Lines • Show All 138 Lines • ▼ Show 20 Lines | |||||
| static int get_next_prid(struct prison **insprp); | static int get_next_prid(struct prison **insprp); | ||||
| static int do_jail_attach(struct thread *td, struct prison *pr, int drflags); | static int do_jail_attach(struct thread *td, struct prison *pr, int drflags); | ||||
| static void prison_complete(void *context, int pending); | static void prison_complete(void *context, int pending); | ||||
| static void prison_deref(struct prison *pr, int flags); | static void prison_deref(struct prison *pr, int flags); | ||||
| static void prison_deref_kill(struct prison *pr, struct prisonlist *freeprison); | static void prison_deref_kill(struct prison *pr, struct prisonlist *freeprison); | ||||
| static int prison_lock_xlock(struct prison *pr, int flags); | static int prison_lock_xlock(struct prison *pr, int flags); | ||||
| static void prison_free_not_last(struct prison *pr); | 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, | static void prison_set_allow_locked(struct prison *pr, unsigned flag, | ||||
| int enable); | int enable); | ||||
| static char *prison_path(struct prison *pr1, struct prison *pr2); | static char *prison_path(struct prison *pr1, struct prison *pr2); | ||||
| #ifdef RACCT | #ifdef RACCT | ||||
| static void prison_racct_attach(struct prison *pr); | static void prison_racct_attach(struct prison *pr); | ||||
| static void prison_racct_modify(struct prison *pr); | static void prison_racct_modify(struct prison *pr); | ||||
| static void prison_racct_detach(struct prison *pr); | static void prison_racct_detach(struct prison *pr); | ||||
| #endif | #endif | ||||
| ▲ Show 20 Lines • Show All 839 Lines • ▼ Show 20 Lines | #endif | ||||
| if (cuflags == JAIL_CREATE && jid == 0 && name != NULL) { | if (cuflags == JAIL_CREATE && jid == 0 && name != NULL) { | ||||
| namelc = strrchr(name, '.'); | namelc = strrchr(name, '.'); | ||||
| jid = strtoul(namelc != NULL ? namelc + 1 : name, &p, 10); | jid = strtoul(namelc != NULL ? namelc + 1 : name, &p, 10); | ||||
| if (*p != '\0') | if (*p != '\0') | ||||
| jid = 0; | jid = 0; | ||||
| } | } | ||||
| sx_xlock(&allprison_lock); | sx_xlock(&allprison_lock); | ||||
| drflags = PD_LIST_XLOCKED; | 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) { | ||||
| if (jid < 0) { | if (jid < 0) { | ||||
| error = EINVAL; | error = EINVAL; | ||||
| vfs_opterror(opts, "negative jid"); | vfs_opterror(opts, "negative jid"); | ||||
| goto done_deref; | goto done_deref; | ||||
| } | } | ||||
| /* | /* | ||||
| * See if a requested jid already exists. Keep track of | * See if a requested jid already exists. Keep track of | ||||
| ▲ Show 20 Lines • Show All 95 Lines • ▼ Show 20 Lines | else { | ||||
| *namelc = '\0'; | *namelc = '\0'; | ||||
| ppr = prison_find_name(mypr, name); | ppr = prison_find_name(mypr, name); | ||||
| if (ppr == NULL) { | if (ppr == NULL) { | ||||
| error = ENOENT; | error = ENOENT; | ||||
| vfs_opterror(opts, | vfs_opterror(opts, | ||||
| "jail \"%s\" not found", name); | "jail \"%s\" not found", name); | ||||
| goto done_deref; | goto done_deref; | ||||
| } | } | ||||
| if (!(flags & JAIL_DYING) && | if (!prison_isalive(ppr)) { | ||||
| !prison_isalive(ppr)) { | |||||
| mtx_unlock(&ppr->pr_mtx); | mtx_unlock(&ppr->pr_mtx); | ||||
| error = ENOENT; | error = ENOENT; | ||||
| vfs_opterror(opts, | vfs_opterror(opts, | ||||
| "jail \"%s\" is dying", name); | "jail \"%s\" is dying", name); | ||||
| goto done_deref; | goto done_deref; | ||||
| } | } | ||||
| mtx_unlock(&ppr->pr_mtx); | mtx_unlock(&ppr->pr_mtx); | ||||
| *namelc = '.'; | *namelc = '.'; | ||||
| ▲ Show 20 Lines • Show All 73 Lines • ▼ Show 20 Lines | #endif | ||||
| if (created) { | if (created) { | ||||
| for (tpr = mypr; tpr != NULL; tpr = tpr->pr_parent) | for (tpr = mypr; tpr != NULL; tpr = tpr->pr_parent) | ||||
| if (tpr->pr_childcount >= tpr->pr_childmax) { | if (tpr->pr_childcount >= tpr->pr_childmax) { | ||||
| error = EPERM; | error = EPERM; | ||||
| vfs_opterror(opts, "prison limit exceeded"); | vfs_opterror(opts, "prison limit exceeded"); | ||||
| goto done_deref; | goto done_deref; | ||||
| } | } | ||||
| prison_hold(ppr); | prison_hold(ppr); | ||||
| if (!refcount_acquire_if_not_zero(&ppr->pr_uref)) { | prison_proc_hold(ppr); | ||||
| /* 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; | |||||
| } | |||||
| } | |||||
| if (jid == 0 && (jid = get_next_prid(&inspr)) == 0) { | if (jid == 0 && (jid = get_next_prid(&inspr)) == 0) { | ||||
| error = EAGAIN; | error = EAGAIN; | ||||
| vfs_opterror(opts, "no available jail IDs"); | vfs_opterror(opts, "no available jail IDs"); | ||||
| pr = ppr; | pr = ppr; | ||||
| drflags |= PD_DEREF | PD_DEUREF; | drflags |= PD_DEREF | PD_DEUREF; | ||||
| goto done_deref; | goto done_deref; | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 1,185 Lines • ▼ Show 20 Lines | |||||
| #endif | #endif | ||||
| PROC_UNLOCK(p); | PROC_UNLOCK(p); | ||||
| #ifdef RCTL | #ifdef RCTL | ||||
| rctl_proc_ucred_changed(p, newcred); | rctl_proc_ucred_changed(p, newcred); | ||||
| crfree(newcred); | crfree(newcred); | ||||
| #endif | #endif | ||||
| prison_deref(oldcred->cr_prison, drflags); | prison_deref(oldcred->cr_prison, drflags); | ||||
| crfree(oldcred); | 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); | return (0); | ||||
| e_unlock: | e_unlock: | ||||
| VOP_UNLOCK(pr->pr_root); | VOP_UNLOCK(pr->pr_root); | ||||
| e_revert_osd: | e_revert_osd: | ||||
| /* Tell modules this thread is still in its old jail after all. */ | /* Tell modules this thread is still in its old jail after all. */ | ||||
| sx_slock(&allprison_lock); | sx_slock(&allprison_lock); | ||||
| drflags |= PD_LIST_SLOCKED; | drflags |= PD_LIST_SLOCKED; | ||||
| ▲ Show 20 Lines • Show All 174 Lines • ▼ Show 20 Lines | |||||
| } | } | ||||
| /* | /* | ||||
| * Hold a a prison for user visibility, by incrementing pr_uref. | * Hold a a prison for user visibility, by incrementing pr_uref. | ||||
| * It is generally an error to hold a prison that isn't already | * It is generally an error to hold a prison that isn't already | ||||
| * user-visible, except through the the jail system calls. It is also | * user-visible, except through the the jail system calls. It is also | ||||
| * an error to hold an invalid prison. A prison record will remain | * 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 | * 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 | void | ||||
| prison_proc_hold(struct prison *pr) | prison_proc_hold(struct prison *pr) | ||||
| { | { | ||||
| #ifdef INVARIANTS | #ifdef INVARIANTS | ||||
| int was_alive = refcount_acquire_if_not_zero(&pr->pr_uref); | int was_alive = refcount_acquire_if_not_zero(&pr->pr_uref); | ||||
| KASSERT(was_alive, | KASSERT(was_alive, | ||||
| Show All 32 Lines | KASSERT(!(pr->pr_flags & PR_COMPLETE_PROC), | ||||
| ("Redundant last reference in prison_proc_free (jid=%d)", | ("Redundant last reference in prison_proc_free (jid=%d)", | ||||
| pr->pr_id)); | pr->pr_id)); | ||||
| pr->pr_flags |= PR_COMPLETE_PROC; | pr->pr_flags |= PR_COMPLETE_PROC; | ||||
| mtx_unlock(&pr->pr_mtx); | mtx_unlock(&pr->pr_mtx); | ||||
| taskqueue_enqueue(taskqueue_thread, &pr->pr_task); | taskqueue_enqueue(taskqueue_thread, &pr->pr_task); | ||||
| } | } | ||||
| } | } | ||||
| 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. | * Complete a call to either prison_free or prison_proc_free. | ||||
| */ | */ | ||||
| static void | static void | ||||
| prison_complete(void *context, int pending) | prison_complete(void *context, int pending) | ||||
| { | { | ||||
| struct prison *pr = context; | struct prison *pr = context; | ||||
| int drflags; | int drflags; | ||||
| Show All 31 Lines | prison_deref(struct prison *pr, int flags) | ||||
| TAILQ_INIT(&freeprison); | TAILQ_INIT(&freeprison); | ||||
| /* | /* | ||||
| * Release this prison as requested, which may cause its parent to be | * Release this prison as requested, which may cause its parent to be | ||||
| * released, and then maybe its grandparent, etc. | * released, and then maybe its grandparent, etc. | ||||
| */ | */ | ||||
| for (;;) { | for (;;) { | ||||
| if (flags & PD_KILL) { | if (flags & PD_KILL) { | ||||
| /* Kill the prison and its descendents. */ | /* Kill the prison and its descendents. */ | ||||
| flags &= ~PD_KILL; | |||||
| if (!(flags & PD_DEREF)) { | if (!(flags & PD_DEREF)) { | ||||
| prison_hold(pr); | prison_hold(pr); | ||||
| flags |= PD_DEREF; | flags |= PD_DEREF; | ||||
| } | } | ||||
| flags = prison_lock_xlock(pr, flags); | flags = prison_lock_xlock(pr, flags); | ||||
| if (pr->pr_state != PRISON_STATE_DYING) { | 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; | |||||
| } | |||||
| prison_deref_kill(pr, &freeprison); | prison_deref_kill(pr, &freeprison); | ||||
| } | } | ||||
| } | |||||
| if (flags & PD_DEUREF) { | if (flags & PD_DEUREF) { | ||||
| /* Drop a user reference. */ | /* Drop a user reference. */ | ||||
| KASSERT(refcount_load(&pr->pr_uref) > 0, | KASSERT(refcount_load(&pr->pr_uref) > 0, | ||||
| ("prison_deref PD_DEUREF on a dead prison (jid=%d)", | ("prison_deref PD_DEUREF on a dead prison (jid=%d)", | ||||
| pr->pr_id)); | pr->pr_id)); | ||||
| if (!refcount_release_if_not_last(&pr->pr_uref)) { | if (!refcount_release_if_not_last(&pr->pr_uref)) { | ||||
| if (!(flags & PD_DEREF)) { | if (!(flags & PD_DEREF)) { | ||||
| prison_hold(pr); | prison_hold(pr); | ||||
| ▲ Show 20 Lines • Show All 134 Lines • ▼ Show 20 Lines | |||||
| /* | /* | ||||
| * Kill the prison and its descendants. Mark them as dying, clear the | * Kill the prison and its descendants. Mark them as dying, clear the | ||||
| * persist flag, and call module remove methods. | * persist flag, and call module remove methods. | ||||
| */ | */ | ||||
| static void | static void | ||||
| prison_deref_kill(struct prison *pr, struct prisonlist *freeprison) | prison_deref_kill(struct prison *pr, struct prisonlist *freeprison) | ||||
| { | { | ||||
| struct prison *cpr, *ppr; | struct prison *cpr, *ppr, *rpr; | ||||
| bool descend; | bool descend; | ||||
| /* | /* | ||||
| * The operation each descendant is similar to what prison_deref() | * The operation for the prison and each descendant is similar to | ||||
| * does when losing the last references, plus clearing PR_PERSIST. | * what prison_deref() does when losing the last references, plus | ||||
| * clearing PR_PERSIST. | |||||
| */ | */ | ||||
| pr->pr_state = PRISON_STATE_DYING; | |||||
| mtx_unlock(&pr->pr_mtx); | mtx_unlock(&pr->pr_mtx); | ||||
| rpr = NULL; | |||||
| FOREACH_PRISON_DESCENDANT_PRE_POST(pr, cpr, descend) { | FOREACH_PRISON_DESCENDANT_PRE_POST(pr, cpr, descend) { | ||||
| if (!prison_isalive(cpr)) | |||||
| continue; | |||||
| if (descend) { | if (descend) { | ||||
| if (!prison_isalive(cpr)) { | |||||
| descend = false; | |||||
| continue; | |||||
| } | |||||
| prison_hold(cpr); | 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; | continue; | ||||
| } | } | ||||
| if (!(cpr->pr_flags & PR_REMOVE)) | |||||
| continue; | |||||
| if (rpr != NULL) { | |||||
| LIST_REMOVE(rpr, pr_sibling); | |||||
| rpr = NULL; | |||||
| } | |||||
| (void)osd_jail_call(cpr, PR_METHOD_REMOVE, NULL); | |||||
| mtx_lock(&cpr->pr_mtx); | mtx_lock(&cpr->pr_mtx); | ||||
| cpr->pr_flags &= ~PR_REMOVE; | |||||
| if (cpr->pr_flags & PR_PERSIST) { | if (cpr->pr_flags & PR_PERSIST) { | ||||
| cpr->pr_flags &= ~PR_PERSIST; | cpr->pr_flags &= ~PR_PERSIST; | ||||
| prison_proc_free_not_last(cpr); | (void)refcount_release(&cpr->pr_uref); | ||||
| prison_free_not_last(cpr); | prison_free_not_last(cpr); | ||||
| } | } | ||||
| 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)) { | if (refcount_release(&cpr->pr_ref)) { | ||||
| cpr->pr_state = PRISON_STATE_INVALID; | rpr = cpr; | ||||
| TAILQ_REMOVE(&allprison, cpr, pr_list); | rpr->pr_state = PRISON_STATE_INVALID; | ||||
| TAILQ_INSERT_TAIL(freeprison, cpr, pr_list); | mtx_unlock(&rpr->pr_mtx); | ||||
| mtx_unlock(&cpr->pr_mtx); | TAILQ_REMOVE(&allprison, rpr, pr_list); | ||||
| ppr = cpr->pr_parent; | TAILQ_INSERT_TAIL(freeprison, rpr, pr_list); | ||||
| prison_proc_free_not_last(ppr); | ppr = rpr->pr_parent; | ||||
| if (!refcount_release_if_not_last(&ppr->pr_uref)) { | |||||
| mtx_lock(&ppr->pr_mtx); | |||||
| (void)refcount_release(&ppr->pr_uref); | |||||
| mtx_unlock(&ppr->pr_mtx); | |||||
| } | |||||
| prison_free_not_last(ppr); | prison_free_not_last(ppr); | ||||
| for (; ppr != NULL; ppr = ppr->pr_parent) | for (; ppr != NULL; ppr = ppr->pr_parent) | ||||
| ppr->pr_childcount--; | ppr->pr_childcount--; | ||||
| } | } | ||||
| else | else | ||||
| mtx_unlock(&cpr->pr_mtx); | mtx_unlock(&cpr->pr_mtx); | ||||
| } | } | ||||
| if (rpr != NULL) | |||||
| LIST_REMOVE(rpr, pr_sibling); | |||||
| (void)osd_jail_call(pr, PR_METHOD_REMOVE, NULL); | |||||
| mtx_lock(&pr->pr_mtx); | mtx_lock(&pr->pr_mtx); | ||||
| if (pr->pr_flags & PR_PERSIST) { | if (pr->pr_flags & PR_PERSIST) { | ||||
| 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); | prison_free_not_last(pr); | ||||
| } | } | ||||
| /* | |||||
| * Disconnect unreferenced descendants from their parents, | |||||
| * which couldn't easily be done mid-loop. | |||||
| */ | |||||
| TAILQ_FOREACH(cpr, freeprison, pr_list) | |||||
| LIST_REMOVE(cpr, pr_sibling); | |||||
| } | } | ||||
| /* | /* | ||||
| * Given the current locking state in the flags, make sure allprison_lock | * Given the current locking state in the flags, make sure allprison_lock | ||||
| * is held exclusive, and the prison is locked. Return flags indicating | * is held exclusive, and the prison is locked. Return flags indicating | ||||
| * the new state. | * the new state. | ||||
| */ | */ | ||||
| static int | static int | ||||
| ▲ Show 20 Lines • Show All 184 Lines • ▼ Show 20 Lines | prison_ischild(struct prison *pr1, struct prison *pr2) | ||||
| for (pr2 = pr2->pr_parent; pr2 != NULL; pr2 = pr2->pr_parent) | for (pr2 = pr2->pr_parent; pr2 != NULL; pr2 = pr2->pr_parent) | ||||
| if (pr1 == pr2) | if (pr1 == pr2) | ||||
| return (1); | return (1); | ||||
| return (0); | return (0); | ||||
| } | } | ||||
| /* | /* | ||||
| * Return true if the prison is currently alive. A prison is alive if it | * Return true if the prison is currently alive. | ||||
| * holds user references. | |||||
| */ | */ | ||||
| bool | bool | ||||
| prison_isalive(struct prison *pr) | prison_isalive(struct prison *pr) | ||||
| { | { | ||||
| if (__predict_false(pr->pr_state != PRISON_STATE_ALIVE)) | if (__predict_false(pr->pr_state != PRISON_STATE_ALIVE)) | ||||
| return (false); | return (false); | ||||
| return (true); | return (true); | ||||
| ▲ Show 20 Lines • Show All 1,425 Lines • Show Last 20 Lines | |||||