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 @@ -136,8 +136,10 @@ struct prisonlist allprison = TAILQ_HEAD_INITIALIZER(allprison); LIST_HEAD(, prison_racct) allprison_racct; int lastprid = 0; +int lastdeadid = 0; static int get_next_prid(struct prison **insprp); +static int get_next_deadid(struct prison **dinsprp); static int do_jail_attach(struct thread *td, struct prison *pr, int drflags); static void prison_complete(void *context, int pending); static void prison_deref(struct prison *pr, int flags); @@ -518,7 +520,7 @@ #endif struct vfsopt *opt; struct vfsoptlist *opts; - struct prison *pr, *deadpr, *inspr, *mypr, *ppr, *tpr; + struct prison *pr, *deadpr, *dinspr, *inspr, *mypr, *ppr, *tpr; struct vnode *root; char *domain, *errmsg, *host, *name, *namelc, *p, *path, *uuid; char *g_path, *osrelstr; @@ -530,10 +532,10 @@ #endif unsigned long hid; size_t namelen, onamelen, pnamelen; - int born, created, cuflags, descend, drflags, enforce; + int created, cuflags, descend, drflags, enforce; int error, errmsg_len, errmsg_pos; int gotchildmax, gotenforce, gothid, gotrsnum, gotslevel; - int jid, jsys, len, level; + int deadid, jid, jsys, len, level; int childmax, osreldt, rsnum, slevel; #if defined(INET) || defined(INET6) int ii, ij; @@ -991,6 +993,7 @@ pr = NULL; ppr = mypr; inspr = NULL; + deadpr = NULL; if (cuflags == JAIL_CREATE && jid == 0 && name != NULL) { namelc = strrchr(name, '.'); jid = strtoul(namelc != NULL ? namelc + 1 : name, &p, 10); @@ -1019,66 +1022,41 @@ continue; if (inspr->pr_id > jid) break; - pr = inspr; - KASSERT(prison_isvalid(pr), + KASSERT(prison_isvalid(inspr), ("Found invalid prison %p", pr)); - mtx_lock(&pr->pr_mtx); - drflags |= PD_LOCKED; + if (prison_isalive(inspr)) { + pr = inspr; + mtx_lock(&pr->pr_mtx); + drflags |= PD_LOCKED; + } else { + /* Note a dying jail to handle later. */ + deadpr = inspr; + } inspr = NULL; break; } - if (pr != NULL) { - ppr = pr->pr_parent; - /* Create: jid must not exist. */ - if (cuflags == JAIL_CREATE) { - /* - * Even creators that cannot see the jail will - * get EEXIST. - */ - error = EEXIST; - vfs_opterror(opts, "jail %d already exists", - jid); - goto done_deref; - } - if (!prison_ischild(mypr, pr)) { - /* - * Updaters get ENOENT if they cannot see the - * jail. This is true even for CREATE | UPDATE, - * which normally cannot give this error. - */ - error = ENOENT; - vfs_opterror(opts, "jail %d not found", jid); - goto done_deref; - } - if (!prison_isalive(pr)) { - if (!(flags & JAIL_DYING)) { - error = ENOENT; - vfs_opterror(opts, "jail %d is dying", - jid); - goto done_deref; - } - if ((flags & JAIL_ATTACH) || - (pr_flags & PR_PERSIST)) { - /* - * A dying jail might be resurrected - * (via attach or persist), but first - * it must determine if another jail - * has claimed its name. Accomplish - * this by implicitly re-setting the - * name. - */ - if (name == NULL) - name = prison_name(mypr, pr); - } - } - } else { - /* Update: jid must exist. */ - if (cuflags == JAIL_UPDATE) { - error = ENOENT; - vfs_opterror(opts, "jail %d not found", jid); - goto done_deref; - } + if (cuflags == JAIL_CREATE && pr != NULL) { + /* + * Even creators that cannot see the jail will get + * EEXIST. + */ + error = EEXIST; + vfs_opterror(opts, "jail %d already exists", jid); + goto done_deref; } + if ((pr == NULL) + ? cuflags == JAIL_UPDATE + : !prison_ischild(mypr, pr)) { + /* + * Updaters get ENOENT for nonexistent jails, or for + * jails they cannot see. The latter case is true + * even for CREATE | UPDATE, which normally cannot + * give this error. + */ + error = ENOENT; + vfs_opterror(opts, "jail %d not found", jid); + goto done_deref; + } } /* * If the caller provided a name, look for a jail by that name. @@ -1115,14 +1093,13 @@ "jail \"%s\" not found", name); goto done_deref; } + mtx_unlock(&ppr->pr_mtx); if (!prison_isalive(ppr)) { - mtx_unlock(&ppr->pr_mtx); error = ENOENT; vfs_opterror(opts, "jail \"%s\" is dying", name); goto done_deref; } - mtx_unlock(&ppr->pr_mtx); *namelc = '.'; } namelc++; @@ -1130,57 +1107,35 @@ if (namelc[0] != '\0') { pnamelen = (ppr == &prison0) ? 0 : strlen(ppr->pr_name) + 1; - deadpr = NULL; FOREACH_PRISON_CHILD(ppr, tpr) { - if (tpr != pr && - !strcmp(tpr->pr_name + pnamelen, namelc)) { - if (prison_isalive(tpr)) { - if (pr == NULL && - cuflags != JAIL_CREATE) { - /* - * Use this jail - * for updates. - */ - pr = tpr; - mtx_lock(&tpr->pr_mtx); - drflags |= PD_LOCKED; - break; - } - /* - * Create, or update(jid): - * name must not exist in an - * active sibling jail. - */ - error = EEXIST; - vfs_opterror(opts, - "jail \"%s\" already exists", - name); - goto done_deref; - } - if (pr == NULL && - cuflags != JAIL_CREATE) { - deadpr = tpr; - KASSERT(prison_isvalid(deadpr), - ("Found invalid prison %p", - deadpr)); - } - } - } - /* If no active jail is found, use a dying one. */ - if (deadpr != NULL && pr == NULL) { - if (flags & JAIL_DYING) { - pr = deadpr; - mtx_lock(&pr->pr_mtx); - drflags |= PD_LOCKED; - } else if (cuflags == JAIL_UPDATE) { - error = ENOENT; + if (tpr == pr || !prison_isalive(tpr) || + strcmp(tpr->pr_name + pnamelen, namelc)) + continue; + KASSERT(prison_isvalid(tpr), + ("Found invalid prison %p", tpr)); + if (cuflags == JAIL_CREATE || pr != NULL) { + /* + * Create, or update(jid): name must + * not exist in an active sibling jail. + */ + error = EEXIST; vfs_opterror(opts, - "jail \"%s\" is dying", name); + "jail \"%s\" already exists", name); goto done_deref; } + /* Use this jail for updates. */ + pr = tpr; + mtx_lock(&pr->pr_mtx); + drflags |= PD_LOCKED; + break; } - /* Update: name must exist if no jid. */ - else if (cuflags == JAIL_UPDATE && pr == NULL) { + /* + * Update: name must exist if no jid. As with the jid + * case, the jail must be currently visible, or else + * even CREATE | UPDATE will get an error. + */ + if ((pr == NULL) + ? cuflags == JAIL_UPDATE : !prison_isalive(pr)) { error = ENOENT; vfs_opterror(opts, "jail \"%s\" not found", name); @@ -1204,14 +1159,40 @@ vfs_opterror(opts, "prison limit exceeded"); goto done_deref; } - prison_hold(ppr); - prison_proc_hold(ppr); + if (deadpr != NULL) { + /* + * The prison being created has the same ID as a dying + * one. Handle this by giving the dying jail a new ID. + * This may cause some confusion to user space, but + * only to those listing dying jails. + */ + deadid = get_next_deadid(&dinspr); + if (deadid == 0) { + error = EAGAIN; + vfs_opterror(opts, "no available jail IDs"); + goto done_deref; + } + mtx_lock(&deadpr->pr_mtx); + deadpr->pr_id = deadid; + mtx_unlock(&deadpr->pr_mtx); + if (dinspr == deadpr) + inspr = deadpr; + else { + inspr = TAILQ_NEXT(deadpr, pr_list); + TAILQ_REMOVE(&allprison, deadpr, pr_list); + if (dinspr != NULL) + TAILQ_INSERT_AFTER(&allprison, dinspr, + deadpr, pr_list); + else + TAILQ_INSERT_HEAD(&allprison, deadpr, + pr_list); + } + } + if (jid == 0 && (jid = get_next_prid(&inspr)) == 0) { error = EAGAIN; vfs_opterror(opts, "no available jail IDs"); - pr = ppr; - drflags |= PD_DEREF | PD_DEUREF; goto done_deref; } @@ -1231,6 +1212,8 @@ TAILQ_INSERT_TAIL(&allprison, pr, pr_list); pr->pr_parent = ppr; + prison_hold(ppr); + prison_proc_hold(ppr); LIST_INSERT_HEAD(&ppr->pr_children, pr, pr_sibling); for (tpr = ppr; tpr != NULL; tpr = tpr->pr_parent) tpr->pr_childcount++; @@ -1736,13 +1719,10 @@ * Persistent prisons get an extra reference, and prisons losing their * persist flag lose that reference. */ - born = !prison_isalive(pr); if (ch_flags & PR_PERSIST & (pr_flags ^ pr->pr_flags)) { if (pr_flags & PR_PERSIST) { prison_hold(pr); - if (refcount_acquire(&pr->pr_uref) == 0 && - pr->pr_state == PRISON_STATE_DYING) - pr->pr_state = PRISON_STATE_ALIVE; + refcount_acquire(&pr->pr_uref); } else { drflags |= PD_DEUREF; prison_free_not_last(pr); @@ -1814,7 +1794,7 @@ #endif /* Let the modules do their work. */ - if (born) { + if (created) { error = osd_jail_call(pr, PR_METHOD_CREATE, opts); if (error) goto done_deref; @@ -1951,6 +1931,55 @@ *insprp = inspr; lastprid = jid; return (jid); +} + +/* + * Find the next available ID for a renumbered dead prison. This is the same + * as get_next_prid, but counting backward from the end of the range. + */ +static int +get_next_deadid(struct prison **dinsprp) +{ + struct prison *dinspr; + int deadid, minid; + + deadid = lastdeadid ? lastdeadid - 1 : JAIL_MAX; + /* + * Take two reverse passes through the allprison list: first starting + * with the proposed deadid, then ending with it. + */ + for (minid = 1; minid != 0; ) { + TAILQ_FOREACH_REVERSE(dinspr, &allprison, prisonlist, pr_list) { + if (dinspr->pr_id > deadid) + continue; + if (dinspr->pr_id < deadid) { + /* Found an opening. */ + minid = 0; + break; + } + if (--deadid < minid) { + if (lastdeadid == minid || lastdeadid == 0) + { + /* + * The entire legal range + * has been traversed + */ + return 0; + } + /* Try again from the start. */ + deadid = JAIL_MAX; + minid = lastdeadid; + break; + } + } + if (dinspr == NULL) { + /* Found room at the end of the list. */ + break; + } + } + *dinsprp = dinspr; + lastdeadid = deadid; + return (deadid); } /* diff --git a/sys/sys/jail.h b/sys/sys/jail.h --- a/sys/sys/jail.h +++ b/sys/sys/jail.h @@ -101,7 +101,7 @@ #define JAIL_UPDATE 0x02 /* Update parameters of existing jail */ #define JAIL_ATTACH 0x04 /* Attach to jail upon creation */ #define JAIL_DYING 0x08 /* Allow getting a dying jail */ -#define JAIL_SET_MASK 0x0f +#define JAIL_SET_MASK 0x0f /* JAIL_DYING is deprecated/ignored here */ #define JAIL_GET_MASK 0x08 #define JAIL_SYS_DISABLE 0