Changeset View
Changeset View
Standalone View
Standalone View
kern/kern_jail.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 138 Lines • ▼ Show 20 Lines | |||||
*/ | */ | ||||
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; | ||||
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); | ||||
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) { | ||||
prison_hold(cpr); | if (!prison_isalive(cpr)) { | ||||
prison_proc_hold(cpr); | descend = false; | ||||
continue; | continue; | ||||
} | } | ||||
prison_hold(cpr); | |||||
mtx_lock(&cpr->pr_mtx); | mtx_lock(&cpr->pr_mtx); | ||||
if (cpr->pr_flags & PR_PERSIST) { | |||||
cpr->pr_flags &= ~PR_PERSIST; | |||||
prison_proc_free_not_last(cpr); | |||||
prison_free_not_last(cpr); | |||||
} | |||||
if (refcount_release(&cpr->pr_uref)) { | |||||
cpr->pr_state = PRISON_STATE_DYING; | cpr->pr_state = PRISON_STATE_DYING; | ||||
cpr->pr_flags |= PR_REMOVE; | |||||
mtx_unlock(&cpr->pr_mtx); | mtx_unlock(&cpr->pr_mtx); | ||||
continue; | |||||
} | |||||
if (!(cpr->pr_flags & PR_REMOVE)) | |||||
continue; | |||||
(void)osd_jail_call(cpr, PR_METHOD_REMOVE, 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) { | |||||
cpr->pr_flags &= ~PR_PERSIST; | |||||
(void)refcount_release(&pr->pr_uref); | |||||
} | } | ||||
if (refcount_release(&cpr->pr_ref)) { | if (refcount_release(&cpr->pr_ref)) { | ||||
cpr->pr_state = PRISON_STATE_INVALID; | cpr->pr_state = PRISON_STATE_INVALID; | ||||
TAILQ_REMOVE(&allprison, cpr, pr_list); | TAILQ_REMOVE(&allprison, cpr, pr_list); | ||||
TAILQ_INSERT_TAIL(freeprison, cpr, pr_list); | TAILQ_INSERT_TAIL(freeprison, cpr, pr_list); | ||||
mtx_unlock(&cpr->pr_mtx); | mtx_unlock(&cpr->pr_mtx); | ||||
ppr = cpr->pr_parent; | ppr = cpr->pr_parent; | ||||
prison_proc_free_not_last(ppr); | |||||
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); | ||||
} | } | ||||
(void)osd_jail_call(cpr, 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); | ||||
} | } | ||||
mtx_unlock(&cpr->pr_mtx); | |||||
/* | /* | ||||
* Disconnect unreferenced descendants from their parents, | * Disconnect unreferenced descendants from their parents, | ||||
* which couldn't easily be done mid-loop. | * which couldn't easily be done mid-loop. | ||||
*/ | */ | ||||
TAILQ_FOREACH(cpr, freeprison, pr_list) | TAILQ_FOREACH(cpr, freeprison, pr_list) | ||||
LIST_REMOVE(cpr, pr_sibling); | LIST_REMOVE(cpr, pr_sibling); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 191 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 |