view usr/src/uts/common/fmac/fmac.c @ 7888:dc5a88b1d093

Fix setting of prev_secid prev_secid is supposed to track the security context prior to the last exec so that applications can get the context of their caller using getprevcon(). This requires the prev_secid to be updated at times other than when the secid is changing. This patch changes the fmac_exec() and gexec() logic accordingly to ensure that the prev_secid is updated as needed. In the case where the prior exec was a secid transition and the current exec is not changing credentials, this requires a new cred in order to update the prev_secid. We may migrate the prev_secid out of the cred and into a per-process structure (as in Linux) in the future. Webrev at: http://cr.opensolaris.org/~sds/prevsecid/
author Stephen Smalley <sds@tycho.nsa.gov>
date Fri, 17 Oct 2008 13:28:56 -0400
parents 76e5e2554169
children 7013fe728e6b
line wrap: on
line source

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * FMAC
 */

#include <sys/types.h>
#include <sys/cmn_err.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/param.h>
#include <sys/kobj.h>
#include <sys/vfs.h>
#include <sys/acl.h>
#include <sys/fmac/security.h>
#include <sys/fmac/fmac.h>
#include <sys/fmac/avc.h>
#include <sys/note.h>

/* Tunables */
int fmac_enabled = 1;		/* policy enabled */
int fmac_enforcing = 0;		/* permissive or enforcing */

char *fmac_default_policy_file = FMAC_POLICY_FILE;

/*
 * Parse boot arguments. Boot arguments take priority over
 * defaults and /etc/system specifications.
 */
static void
fmac_parse_bootargs(const char *cp)
{
	const char *ncp;

	while (*cp != '\0') {

		/* Skip white spaces */
		while (*cp == ' ')
			cp++;

		if (strncmp(cp, "enabled", sizeof ("enabled") -1) == 0) {
			fmac_enabled = 1;
			cp += sizeof ("enabled") - 1;
		} else if (strncmp(cp, "disabled",
		    sizeof ("disabled")-1) == 0) {
			fmac_enabled = 0;
			cp += sizeof ("disabled") - 1;
		} else if (strncmp(cp, "enforcing",
		    sizeof ("enforcing")-1) == 0) {
			fmac_enabled = 1;
			fmac_enforcing = 1;
			cp += sizeof ("enforcing") - 1;
		} else if (strncmp(cp, "permissive",
		    sizeof ("permissive")-1) == 0) {
			fmac_enabled = 1;
			fmac_enforcing = 0;
			cp += sizeof ("permissive") - 1;
		}

		/* Check for additional arguments */
		if (*cp != '\0' && (ncp = strchr(cp, ',')) != '\0') {
			cp = ncp + 1;
		} else
			break;
	}
}

void
fmac_init()
{
	fmac_parse_bootargs(policyargs);

	if (fmac_enabled)
		if (fmac_load_policy(fmac_default_policy_file))
			if (fmac_enforcing)
				cmn_err(CE_PANIC,
				    "security: Policy load failed");
}

int
fmac_load_policy(char *file)
{
	struct _buf *policy_handle;
	int ret;

	cmn_err(CE_CONT, "security: Loading policy %s\n", file);

	if ((policy_handle = kobj_open_file(file)) ==
	    (struct _buf *)-1) {
		cmn_err(CE_WARN, "security: Unable to open %s\n", file);
		return (ENOENT);
	}

	if ((ret = security_load_policy(policy_handle, 0))) {
		cmn_err(CE_WARN, "security: Policy load failed %s\n", file);
		kobj_close_file(policy_handle);
		return (ret);
	}

	kobj_close_file(policy_handle);

	cmn_err(CE_CONT, "security: Policy loaded from %s\n", file);
	cmn_err(CE_CONT, "security: mode is %s\n",
	    fmac_enforcing == 0 ? "permissive" : "enforcing");

	return (0);
}

security_class_t
fmac_vtype_to_sclass(vtype_t vtype)
{
	switch (vtype) {
	case VREG:
		return (SECCLASS_FILE);
	case VDIR:
		return (SECCLASS_DIR);
#if notyet
	/* Wait until we have labeling support for all file types. */
	case VBLK:
		return (SECCLASS_BLK_FILE);
	case VCHR:
		return (SECCLASS_CHR_FILE);
	case VLNK:
		return (SECCLASS_LNK_FILE);
	case VFIFO:
		return (SECCLASS_FIFO_FILE);
	case VSOCK:
		return (SECCLASS_SOCK_FILE);
#endif
	case VDOOR:
		/* TBD */
	case VPROC:
		/* TBD */
	case VPORT:
		/* TBD */
	case VNON:
		return (SECCLASS_NULL);
	}
	return (SECCLASS_NULL);
}

int
fmac_vnode_lookup(vnode_t *vp, cred_t *cr, caller_context_t *ct)
{
	int error;
	xvattr_t xvattr;
	xoptattr_t *xoap;
	security_id_t secid;

	if (!fmac_enabled)
		return (0);
	if (vp->v_secid != SECINITSID_UNLABELED)
		return (0); /* already set */
	if (vfs_has_feature(vp->v_vfsp, VFSFT_XVATTR) == 0)
		return (0);

	xva_init(&xvattr);
	if ((xoap = xva_getxoptattr(&xvattr)) == NULL)
		return (EINVAL);
	XVA_SET_REQ(&xvattr, XAT_SECCTX);

	error = VOP_GETATTR(vp, &xvattr.xva_vattr, 0, cr, ct);
	if (error)
		return (error);

	if (XVA_ISSET_RTN(&xvattr, XAT_SECCTX)) {
		error = security_context_to_sid(xoap->xoa_secctx,
		    strlen(xoap->xoa_secctx), &secid);
		if (error)
			return (error);
	} else {
		/* default SID for files without a secctx. */
		secid = SECINITSID_FILE;
	}

	mutex_enter(&(vp->v_lock));
	if (vp->v_secid == SECINITSID_UNLABELED)
		vp->v_secid = secid;
	mutex_exit(&(vp->v_lock));

	return (0);
}

void
fmac_vnode_init_secid(vnode_t *vp, char *secctx)
{
	security_id_t secid;

	if (!fmac_enabled)
		return;

	/*
	 * Called before vp is put in dnlc, so no need to hold v_lock.
	 */
	if (security_context_to_sid(secctx, strlen(secctx), &secid))
		vp->v_secid = SECINITSID_UNLABELED;
	else
		vp->v_secid = secid;
}

int
fmac_vfs_root(vfs_t *vfsp, vnode_t *vp)
{
	_NOTE(ARGUNUSED(vfsp));	/* future use for context mounts? */
	return (fmac_vnode_lookup(vp, CRED(), NULL));
}

int
fmac_vnode_set_secctx(char *secctx, cred_t *cr, vtype_t vtype, vnode_t *vp)
{
	security_id_t cr_secid, old_secid, new_secid;
	security_class_t sclass;
	int error;
	avc_audit_data_t ad;

	if (!fmac_enabled)
		return (EINVAL);

	cr_secid = crgetsecid(cr);

	sclass = fmac_vtype_to_sclass(vtype);
	if (!sclass)
		return (EINVAL);

	error = security_context_to_sid(secctx, strlen(secctx), &new_secid);
	if (error)
		return (error);

	if (vp) {
		/*
		 * Relabeling an existing file.
		 */
		mutex_enter(&(vp->v_lock));
		old_secid = vp->v_secid;
		AVC_AUDIT_DATA_INIT(&ad, FS);
		ad.u.fs.vp = vp;
		error = avc_has_perm(cr_secid, old_secid, sclass,
		    FILE__RELABELFROM, &ad);
		if (!error)
			error = avc_has_perm(cr_secid, new_secid, sclass,
			    FILE__RELABELTO, &ad);
		if (!error)
			vp->v_secid = new_secid;
		mutex_exit(&(vp->v_lock));
	} else {
		/* Creating a new file. */
		error = avc_has_perm(cr_secid, new_secid, sclass,
		    FILE__CREATE, NULL);
	}

	return (error);
}

int
fmac_vnode_get_secctx(vnode_t *vp, vattr_t *vap)
{
	xvattr_t *xvap = (xvattr_t *)vap;
	xoptattr_t *xoap;
	security_context_t scontext;
	uint32_t scontext_len;
	int error;

	if (!fmac_enabled)
		return (0);

	xoap = xva_getxoptattr(xvap);
	if (!xoap)
		return (0);

	if (!XVA_ISSET_REQ(xvap, XAT_SECCTX))
		return (0);

	error = security_sid_to_context(vp->v_secid, &scontext, &scontext_len);
	if (error)
		return (error);

	if (scontext_len > sizeof (xoap->xoa_secctx)) {
		security_context_free(scontext);
		return (EINVAL);
	}

	(void) strncpy(xoap->xoa_secctx, scontext, sizeof (xoap->xoa_secctx));
	XVA_SET_RTN(xvap, XAT_SECCTX);
	security_context_free(scontext);
	return (0);
}

int
fmac_vnode_create(vnode_t *dvp, char *name, xvattr_t *xvap, vattr_t **vapp,
    cred_t *cr, security_id_t *secidp)
{
	security_id_t cr_secid, secid;
	security_class_t sclass;
	security_context_t scontext;
	uint32_t scontext_len;
	vattr_t *vap = *vapp;
	xoptattr_t *xoap;
	int error;
	avc_audit_data_t ad;

	if (!fmac_enabled)
		return (0);

	/*
	 * Make sure we define a default secid for use by
	 * fmac_vnode_post_create even if the fs does not
	 * support xvattrs.
	 */
	*secidp = SECINITSID_FILE;

	if (vfs_has_feature(dvp->v_vfsp, VFSFT_XVATTR) == 0)
		return (0);

	sclass = fmac_vtype_to_sclass(vap->va_type);
	if (!sclass)
		return (0);

	cr_secid = crgetsecid(cr);

	error = security_transition_sid(cr_secid, dvp->v_secid, sclass,
	    &secid);
	if (error)
		return (error);

	AVC_AUDIT_DATA_INIT(&ad, FS);
	ad.u.fs.vp = dvp;
	ad.u.fs.name = name;

	error = avc_has_perm(cr_secid, dvp->v_secid, SECCLASS_DIR,
	    DIR__ADD_NAME, &ad);
	if (error)
		return (error);

	error = avc_has_perm(cr_secid, secid, sclass, FILE__CREATE, &ad);
	if (error)
		return (error);

	if (!xvap) {
		/*
		 * Caller only wants the secid, not an xvattr w/ secctx.
		 * tmpfs is one such example.
		 */
		*secidp = secid;
		return (0);
	}

	if (!(vap->va_mask & AT_XVATTR)) {
		/*
		 * If the vattr is not already an xvattr, then wrap the
		 * vattr with an xvattr so we can pass the secctx to
		 * the fs code.
		 */
		xva_from_va(xvap, vap);
		*vapp = &xvap->xva_vattr;
	} else {
		xvap = (xvattr_t *)vap;
	}

	error = security_sid_to_context(secid, &scontext, &scontext_len);
	if (error)
		return (error);

	xoap = xva_getxoptattr(xvap);
	if (!xoap || scontext_len > sizeof (xoap->xoa_secctx))
		goto inval;
	(void) strncpy(xoap->xoa_secctx, scontext, sizeof (xoap->xoa_secctx));
	XVA_SET_REQ(xvap, XAT_SECCTX);

	*secidp = secid;
	security_context_free(scontext);
	return (0);
inval:
	security_context_free(scontext);
	return (EINVAL);
}

void
fmac_vnode_post_create(vnode_t *vp, security_id_t secid)
{
	if (!fmac_enabled)
		return;
	mutex_enter(&(vp->v_lock));
	vp->v_secid = secid;
	mutex_exit(&(vp->v_lock));
}

int
fmac_vnode_link(vnode_t *tdvp, vnode_t *svp, char *name, cred_t *cr)
{
	security_id_t cr_secid;
	security_class_t sclass;
	int error;
	avc_audit_data_t ad;

	if (!fmac_enabled)
		return (0);

	sclass = fmac_vtype_to_sclass(svp->v_type);
	if (!sclass)
		return (0);

	cr_secid = crgetsecid(cr);

	AVC_AUDIT_DATA_INIT(&ad, FS);
	ad.u.fs.vp = tdvp;
	ad.u.fs.name = name;
	error = avc_has_perm(cr_secid, tdvp->v_secid, SECCLASS_DIR,
	    DIR__ADD_NAME, &ad);
	if (error)
		return (error);

	ad.u.fs.vp = svp;
	return (avc_has_perm(cr_secid, svp->v_secid, sclass,
	    FILE__LINK, &ad));
}

int
fmac_vnode_remove(vnode_t *dvp, vnode_t *vp, char *name, cred_t *cr)
{
	security_id_t cr_secid;
	security_class_t sclass;
	access_vector_t av;
	int error;
	avc_audit_data_t ad;

	if (!fmac_enabled)
		return (0);

	sclass = fmac_vtype_to_sclass(vp->v_type);
	if (!sclass)
		return (0);

	cr_secid = crgetsecid(cr);

	AVC_AUDIT_DATA_INIT(&ad, FS);
	ad.u.fs.vp = dvp;
	ad.u.fs.name = name;
	error = avc_has_perm(cr_secid, dvp->v_secid, SECCLASS_DIR,
	    DIR__REMOVE_NAME, &ad);
	if (error)
		return (error);

	ad.u.fs.vp = vp;
	if (sclass == SECCLASS_DIR)
		av = DIR__RMDIR;
	else
		av = FILE__UNLINK;
	return (avc_has_perm(cr_secid, vp->v_secid, sclass, av, &ad));
}

int
fmac_vnode_rename(vnode_t *sdvp, vnode_t *svp, vnode_t *tdvp, vnode_t *tvp,
    cred_t *cr)
{
	security_id_t cr_secid;
	security_class_t sclass, tclass;
	access_vector_t av;
	int error;
	avc_audit_data_t ad;

	if (!fmac_enabled)
		return (0);

	sclass = fmac_vtype_to_sclass(svp->v_type);
	if (!sclass)
		return (0);

	cr_secid = crgetsecid(cr);

	AVC_AUDIT_DATA_INIT(&ad, FS);

	ad.u.fs.vp = sdvp;
	error = avc_has_perm(cr_secid, sdvp->v_secid, SECCLASS_DIR,
	    DIR__REMOVE_NAME, &ad);
	if (error)
		return (error);

	ad.u.fs.vp = svp;
	error = avc_has_perm(cr_secid, svp->v_secid, sclass,
	    FILE__RENAME, &ad);
	if (error)
		return (error);

	ad.u.fs.vp = tdvp;
	error = avc_has_perm(cr_secid, tdvp->v_secid, SECCLASS_DIR,
	    DIR__ADD_NAME, &ad);
	if (error)
		return (error);

	if (tvp) {
		tclass = fmac_vtype_to_sclass(tvp->v_type);
		if (!tclass)
			return (0);

		if (tclass == SECCLASS_DIR)
			av = DIR__RMDIR;
		else
			av = FILE__UNLINK;

		ad.u.fs.vp = tvp;
		error = avc_has_perm(cr_secid, tvp->v_secid, tclass, av,
		    &ad);
		if (error)
			return (error);
	}

	return (0);
}

int
fmac_vnode_setattr(vnode_t *vp, cred_t *cr)
{
	security_id_t cr_secid;
	security_class_t sclass;
	avc_audit_data_t ad;

	if (!fmac_enabled)
		return (0);

	sclass = fmac_vtype_to_sclass(vp->v_type);
	if (!sclass)
		return (0);

	cr_secid = crgetsecid(cr);

	AVC_AUDIT_DATA_INIT(&ad, FS);
	ad.u.fs.vp = vp;
	return (avc_has_perm(cr_secid, vp->v_secid, sclass,
	    FILE__SETATTR, &ad));
}

int
fmac_exec(cred_t *cr, vnode_t *vp, boolean_t *setsecid,
    boolean_t *execsetid, security_id_t *prev_secidp, security_id_t *secidp)
{
	security_id_t prev_secid, secid;
	int error;
	avc_audit_data_t ad;

	if (!fmac_enabled)
		return (0);

	prev_secid = crgetsecid(cr);
	secid = crgetexecsecid(cr);
	if (!secid) {
		error = security_transition_sid(prev_secid, vp->v_secid,
		    SECCLASS_PROCESS, &secid);
		if (error)
			return (error);
	}

	AVC_AUDIT_DATA_INIT(&ad, FS);
	ad.u.fs.vp = vp;

	if (prev_secid == secid) {
		error = avc_has_perm(prev_secid, vp->v_secid,
		    SECCLASS_FILE, FILE__EXECUTE_NO_TRANS, &ad);
		if (error)
			return (error);
		*execsetid = B_FALSE;
		*setsecid = B_FALSE;
		*prev_secidp = *secidp = secid;
		return (0);
	}

	error = avc_has_perm(prev_secid, secid, SECCLASS_PROCESS,
	    PROCESS__TRANSITION, &ad);
	if (error)
		return (error);

	error = avc_has_perm(secid, vp->v_secid, SECCLASS_FILE,
	    FILE__ENTRYPOINT, &ad);
	if (error)
		return (error);

	error = avc_has_perm(prev_secid, secid, SECCLASS_PROCESS,
	    PROCESS__EXECSETID, &ad);
	if (error)
		*execsetid = B_TRUE;
	else
		*execsetid = B_FALSE;

	*setsecid = B_TRUE;
	*prev_secidp = prev_secid;
	*secidp = secid;
	return (0);
}

#define	fmac_ace_to_av(mask, perm) \
	if (mode & (mask)) { \
		mode &= ~(mask); \
		av |= (perm); \
	}

#define	ACE_GETATTR_MASK (ACE_READ_NAMED_ATTRS | ACE_READ_ATTRIBUTES | \
    ACE_READ_ACL)
#define	ACE_SETATTR_MASK (ACE_WRITE_NAMED_ATTRS | ACE_WRITE_ATTRIBUTES | \
    ACE_WRITE_ACL | ACE_WRITE_OWNER)

int
fmac_vnode_access(vnode_t *vp, int mode, int flags, cred_t *cr,
    boolean_t audit)
{
	security_id_t cr_secid;
	security_class_t sclass;
	access_vector_t av;
	avc_audit_data_t ad;

	if (!fmac_enabled)
		return (0);

	cr_secid = crgetsecid(cr);

	sclass = fmac_vtype_to_sclass(vp->v_type);
	if (!sclass)
		return (0);

	av = 0;

	if (flags & V_ACE_MASK) {
		mode &= ~ACE_SYNCHRONIZE; /* ignore synchronize bit */
		fmac_ace_to_av(ACE_READ_DATA, FILE__READ);
		fmac_ace_to_av(ACE_GETATTR_MASK, FILE__GETATTR);
		fmac_ace_to_av(ACE_SETATTR_MASK, FILE__SETATTR);
		if (sclass == SECCLASS_DIR) {
			fmac_ace_to_av((ACE_ADD_FILE | ACE_ADD_SUBDIRECTORY),
			    DIR__ADD_NAME);
			fmac_ace_to_av(ACE_DELETE_CHILD, DIR__REMOVE_NAME);
			fmac_ace_to_av(ACE_DELETE, DIR__RMDIR);
			fmac_ace_to_av(ACE_EXECUTE, DIR__SEARCH);
		} else {
			fmac_ace_to_av(ACE_APPEND_DATA, FILE__APPEND);
			fmac_ace_to_av(ACE_WRITE_DATA, (flags & V_APPEND) ?
			    FILE__APPEND : FILE__WRITE);
			fmac_ace_to_av(ACE_EXECUTE, FILE__EXECUTE);
			fmac_ace_to_av(ACE_DELETE, FILE__UNLINK);
		}
		if (mode) {
			cmn_err(CE_WARN, "FMAC:  Unknown ACE mask 0x%x\n",
			    mode);
			return (EACCES);
		}
	} else {
		if (mode & VREAD)
			av |= FILE__READ;
		if (flags & V_APPEND)
			av |= FILE__APPEND;
		else if (mode & VWRITE)
			av |= FILE__WRITE;
		if (mode & VEXEC) {
			if (sclass == SECCLASS_DIR)
				av |= DIR__SEARCH;
			else
				av |= FILE__EXECUTE;
		}
	}

	if (!av)
		return (0);

	if (audit) {
		AVC_AUDIT_DATA_INIT(&ad, FS);
		ad.u.fs.vp = vp;
	} else
		AVC_AUDIT_DATA_INIT(&ad, DONTAUDIT);
	return (avc_has_perm(cr_secid, vp->v_secid, sclass, av, &ad));
}

int
fmac_priv_proc_cred_perm(const cred_t *scr, cred_t *tcr, int mode)
{
	_NOTE(ARGUNUSED(mode));	/* todo:  distinguish read vs. write? */

	if (!fmac_enabled)
		return (0);
	return (avc_has_perm(crgetsecid((cred_t *)scr), crgetsecid(tcr),
	    SECCLASS_PROCESS, PROCESS__PTRACE, NULL));
}

access_vector_t
fmac_sigtoav(int sig)
{
	switch (sig) {
	case SIGCHLD:
		return (PROCESS__SIGCHLD);
	case SIGKILL:
		return (PROCESS__SIGKILL);
	case SIGSTOP:
		return (PROCESS__SIGSTOP);
	}
	return (PROCESS__SIGNAL);
}

int
fmac_hasprocperm(const cred_t *tcrp, const cred_t *scrp, access_vector_t perms)
{
	security_id_t tsecid;
	security_id_t ssecid;

	if (!fmac_enabled)
		return (0);

	tsecid = crgetsecid((cred_t *)tcrp);
	ssecid = crgetsecid((cred_t *)scrp);
	return (avc_has_perm(ssecid, tsecid, SECCLASS_PROCESS, perms, NULL));
}