jail: Consistently handle the pr_allow bitmask

Return a boolean (i.e. 0 or 1) from prison_allow, instead of the flag
value itself, which is what sysctl expects.

Add prison_set_allow(), which can set or clear a permission bit, and
propagates cleared bits down to child jails.

Use prison_allow() and prison_set_allow() in the various jail.allow.*
sysctls, and others that depend on thoe permissions.

Add locking around checking both pr_allow and pr_enforce_statfs in
prison_priv_check().
This commit is contained in:
Jamie Gritton 2020-12-26 20:25:02 -08:00
parent 43c2734895
commit 0fe74ae624
5 changed files with 90 additions and 80 deletions

View File

@ -140,6 +140,8 @@ static int get_next_prid(struct prison **insprp);
static int do_jail_attach(struct thread *td, struct prison *pr);
static void prison_complete(void *context, int pending);
static void prison_deref(struct prison *pr, int flags);
static void prison_set_allow_locked(struct prison *pr, unsigned flag,
int enable);
static char *prison_path(struct prison *pr1, struct prison *pr2);
static void prison_remove_one(struct prison *pr);
#ifdef RACCT
@ -1726,12 +1728,9 @@ kern_jail_set(struct thread *td, struct uio *optuio, int flags)
}
}
}
if ((tallow = ch_allow & ~pr_allow)) {
/* Clear allow bits in all children. */
FOREACH_PRISON_DESCENDANT_LOCKED(pr, tpr, descend)
tpr->pr_allow &= ~tallow;
}
pr->pr_allow = (pr->pr_allow & ~ch_allow) | pr_allow;
if ((tallow = ch_allow & ~pr_allow))
prison_set_allow_locked(pr, tallow, 0);
/*
* Persistent prisons get an extra reference, and prisons losing their
* persist flag lose that reference. Only do this for existing prisons
@ -2589,13 +2588,15 @@ prison_find_name(struct prison *mypr, const char *name)
}
/*
* See if a prison has the specific flag set.
* See if a prison has the specific flag set. The prison should be locked,
* unless checking for flags that are only set at jail creation (such as
* PR_IP4 and PR_IP6), or only the single bit is examined, without regard
* to any other prison data.
*/
int
prison_flag(struct ucred *cred, unsigned flag)
{
/* This is an atomic read, so no locking is necessary. */
return (cred->cr_prison->pr_flags & flag);
}
@ -2603,8 +2604,7 @@ int
prison_allow(struct ucred *cred, unsigned flag)
{
/* This is an atomic read, so no locking is necessary. */
return (cred->cr_prison->pr_allow & flag);
return ((cred->cr_prison->pr_allow & flag) != 0);
}
/*
@ -2802,6 +2802,38 @@ prison_proc_free(struct prison *pr)
mtx_unlock(&pr->pr_mtx);
}
/*
* Set or clear a permission bit in the pr_allow field, passing restrictions
* (cleared permission) down to child jails.
*/
void
prison_set_allow(struct ucred *cred, unsigned flag, int enable)
{
struct prison *pr;
pr = cred->cr_prison;
sx_slock(&allprison_lock);
mtx_lock(&pr->pr_mtx);
prison_set_allow_locked(pr, flag, enable);
mtx_unlock(&pr->pr_mtx);
sx_sunlock(&allprison_lock);
}
static void
prison_set_allow_locked(struct prison *pr, unsigned flag, int enable)
{
struct prison *cpr;
int descend;
if (enable != 0)
pr->pr_allow |= flag;
else {
pr->pr_allow &= ~flag;
FOREACH_PRISON_DESCENDANT_LOCKED(pr, cpr, descend)
cpr->pr_allow &= ~flag;
}
}
/*
* Check if a jail supports the given address family.
*
@ -3117,6 +3149,8 @@ prison_enforce_statfs(struct ucred *cred, struct mount *mp, struct statfs *sp)
int
prison_priv_check(struct ucred *cred, int priv)
{
struct prison *pr;
int error;
/*
* Some policies have custom handlers. This routine should not be
@ -3388,11 +3422,14 @@ prison_priv_check(struct ucred *cred, int priv)
case PRIV_VFS_UNMOUNT:
case PRIV_VFS_MOUNT_NONUSER:
case PRIV_VFS_MOUNT_OWNER:
if (cred->cr_prison->pr_allow & PR_ALLOW_MOUNT &&
cred->cr_prison->pr_enforce_statfs < 2)
return (0);
pr = cred->cr_prison;
prison_lock(pr);
if (pr->pr_allow & PR_ALLOW_MOUNT && pr->pr_enforce_statfs < 2)
error = 0;
else
return (EPERM);
error = EPERM;
prison_unlock(pr);
return (error);
/*
* Jails should hold no disposition on the PRIV_VFS_READ_DIR
@ -3685,14 +3722,16 @@ SYSCTL_UINT(_security_jail, OID_AUTO, jail_max_af_ips, CTLFLAG_RW,
static int
sysctl_jail_default_allow(SYSCTL_HANDLER_ARGS)
{
struct prison *pr;
int allow, error, i;
pr = req->td->td_ucred->cr_prison;
allow = (pr == &prison0) ? jail_default_allow : pr->pr_allow;
int error, i;
/* Get the current flag value, and convert it to a boolean. */
i = (allow & arg2) ? 1 : 0;
if (req->td->td_ucred->cr_prison == &prison0) {
mtx_lock(&prison0.pr_mtx);
i = (jail_default_allow & arg2) != 0;
mtx_unlock(&prison0.pr_mtx);
} else
i = prison_allow(req->td->td_ucred, arg2);
if (arg1 != NULL)
i = !i;
error = sysctl_handle_int(oidp, &i, 0, req);

View File

@ -346,25 +346,27 @@ sysctl_hostname(SYSCTL_HANDLER_ARGS)
KASSERT(len <= sizeof(tmpname),
("length %d too long for %s", len, __func__));
pr = req->td->td_ucred->cr_prison;
if (!(pr->pr_allow & PR_ALLOW_SET_HOSTNAME) && req->newptr)
return (EPERM);
/*
* Make a local copy of hostname to get/set so we don't have to hold
* the jail mutex during the sysctl copyin/copyout activities.
*/
pr = req->td->td_ucred->cr_prison;
mtx_lock(&pr->pr_mtx);
bcopy((char *)pr + pr_offset, tmpname, len);
mtx_unlock(&pr->pr_mtx);
error = sysctl_handle_string(oidp, tmpname, len, req);
if (error != 0 || req->newptr == NULL)
return (error);
if (req->newptr != NULL && error == 0) {
/*
* Copy the locally set hostname to all jails that share
* this host info.
*/
sx_slock(&allprison_lock);
/*
* Copy the locally set hostname to all jails that share
* this host info.
*/
sx_slock(&allprison_lock);
if (!(pr->pr_allow & PR_ALLOW_SET_HOSTNAME))
error = EPERM;
else {
while (!(pr->pr_flags & PR_HOST))
pr = pr->pr_parent;
mtx_lock(&pr->pr_mtx);
@ -375,8 +377,8 @@ sysctl_hostname(SYSCTL_HANDLER_ARGS)
else
bcopy(tmpname, (char *)cpr + pr_offset, len);
mtx_unlock(&pr->pr_mtx);
sx_sunlock(&allprison_lock);
}
sx_sunlock(&allprison_lock);
return (error);
}
@ -465,13 +467,18 @@ sysctl_hostid(SYSCTL_HANDLER_ARGS)
* instead of a string, and is used only for hostid.
*/
pr = req->td->td_ucred->cr_prison;
if (!(pr->pr_allow & PR_ALLOW_SET_HOSTNAME) && req->newptr)
return (EPERM);
mtx_lock(&pr->pr_mtx);
tmpid = pr->pr_hostid;
error = sysctl_handle_long(oidp, &tmpid, 0, req);
mtx_unlock(&pr->pr_mtx);
if (req->newptr != NULL && error == 0) {
sx_slock(&allprison_lock);
error = sysctl_handle_long(oidp, &tmpid, 0, req);
if (error != 0 || req->newptr == NULL)
return (error);
sx_slock(&allprison_lock);
if (!(pr->pr_allow & PR_ALLOW_SET_HOSTNAME))
error = EPERM;
else {
while (!(pr->pr_flags & PR_HOST))
pr = pr->pr_parent;
mtx_lock(&pr->pr_mtx);
@ -482,8 +489,8 @@ sysctl_hostid(SYSCTL_HANDLER_ARGS)
else
cpr->pr_hostid = tmpid;
mtx_unlock(&pr->pr_mtx);
sx_sunlock(&allprison_lock);
}
sx_sunlock(&allprison_lock);
return (error);
}

View File

@ -63,46 +63,21 @@ static bool
suser_enabled(struct ucred *cred)
{
return (prison_allow(cred, PR_ALLOW_SUSER) ? true : false);
}
static void inline
prison_suser_set(struct prison *pr, int enabled)
{
if (enabled) {
pr->pr_allow |= PR_ALLOW_SUSER;
} else {
pr->pr_allow &= ~PR_ALLOW_SUSER;
}
return (prison_allow(cred, PR_ALLOW_SUSER));
}
static int
sysctl_kern_suser_enabled(SYSCTL_HANDLER_ARGS)
{
struct prison *pr, *cpr;
struct ucred *cred;
int descend, error, enabled;
int error, enabled;
cred = req->td->td_ucred;
enabled = suser_enabled(cred);
error = sysctl_handle_int(oidp, &enabled, 0, req);
if (error || !req->newptr)
return (error);
pr = cred->cr_prison;
sx_slock(&allprison_lock);
mtx_lock(&pr->pr_mtx);
prison_suser_set(pr, enabled);
if (!enabled) {
FOREACH_PRISON_DESCENDANT_LOCKED(pr, cpr, descend) {
prison_suser_set(cpr, 0);
}
}
mtx_unlock(&pr->pr_mtx);
sx_sunlock(&allprison_lock);
prison_set_allow(cred, PR_ALLOW_SUSER, enabled);
return (0);
}

View File

@ -1646,28 +1646,16 @@ p_cansched(struct thread *td, struct proc *p)
static int
sysctl_unprivileged_proc_debug(SYSCTL_HANDLER_ARGS)
{
struct prison *pr;
int error, val;
val = prison_allow(req->td->td_ucred, PR_ALLOW_UNPRIV_DEBUG) != 0;
val = prison_allow(req->td->td_ucred, PR_ALLOW_UNPRIV_DEBUG);
error = sysctl_handle_int(oidp, &val, 0, req);
if (error != 0 || req->newptr == NULL)
return (error);
pr = req->td->td_ucred->cr_prison;
mtx_lock(&pr->pr_mtx);
switch (val) {
case 0:
pr->pr_allow &= ~(PR_ALLOW_UNPRIV_DEBUG);
break;
case 1:
pr->pr_allow |= PR_ALLOW_UNPRIV_DEBUG;
break;
default:
error = EINVAL;
}
mtx_unlock(&pr->pr_mtx);
return (error);
if (val != 0 && val != 1)
return (EINVAL);
prison_set_allow(req->td->td_ucred, PR_ALLOW_UNPRIV_DEBUG, val);
return (0);
}
/*

View File

@ -405,6 +405,7 @@ void prison_hold(struct prison *pr);
void prison_hold_locked(struct prison *pr);
void prison_proc_hold(struct prison *);
void prison_proc_free(struct prison *);
void prison_set_allow(struct ucred *cred, unsigned flag, int enable);
int prison_ischild(struct prison *, struct prison *);
int prison_equal_ip4(struct prison *, struct prison *);
int prison_get_ip4(struct ucred *cred, struct in_addr *ia);