Re: [RFC][PATCH 1/3] EVM

From: Stephen Smalley (sds@private)
Date: Tue Nov 15 2005 - 10:09:31 PST


On Tue, 2005-11-15 at 09:08 -0500, David Safford wrote: 
> diff -rpuN linux-2.6.14.2.orig/security/evm/evm_cache.c linux-2.6.14.2.tc/security/evm/evm_cache.c
> --- linux-2.6.14.2.orig/security/evm/evm_cache.c	1969-12-31 19:00:00.000000000 -0500
> +++ linux-2.6.14.2.tc/security/evm/evm_cache.c	2005-11-11 13:48:30.000000000 -0500
<snip> 
> +static int evm_getxattr(struct dentry *dentry, char *xattrName,
> +			char **xattr_value, int xattr_size)
> +{
> +	char *value = *xattr_value;
> +	int size;
> +	int error;
> +
> +	if (!dentry || !dentry->d_inode || !dentry->d_inode->i_op
> +	    || !dentry->d_inode->i_op->getxattr)
> +		return xattr_size;

You should only apply this kind of checking for those pointers that can
be legitimately NULL here.  Otherwise, you are just covering up more
serious bugs.  Same issue occurs throughout the code, so please fix it
everywhere.  Also, returning xattr_size here seems likely to confuse the
caller, as it will then try to use the value buffer which hasn't been
initialized.

> +static int evm_verify_hmac(struct dentry *dentry, struct inode *inode)
> +{
<snip>
> +	rc = evm_calc_hmac(dentry, hmacVal);
> +	if (rc < 0)
> +		return rc;
<snip>
> +	rc = evm_verify_xattr(dentry, hmacVal, "security.evm.hmac",
> +			      trustedHMAC, SHA1_DIGEST_SIZE);

The attributes over which the HMAC is being computed can change prior to
or during the verification.  And this is ok?  I see that you attempt to
provide access control over such changes partially via EVM's own
inode_setxattr hook and partially via the other LSM's inode_setxattr
hook, but if we are relying on the access control mechanism at runtime
for such protection, then what precisely do we gain from performing such
verification on inode_permission and file_permission (versus a one-time
verification when the inode is first d_instantiated and then using the
access control mechanism subsequently)?

> +int evm_inode_permission(struct inode *inode, int mask,
> +			 struct nameidata *nd)
> +{
> +	struct evm_isec_cache *isec;
> +	struct evm_isec_xattr *istatus;
> +	struct dentry *dentry;
> +	ssize_t error = 0;
> +
> +	int xattr_size = 0;
> +	char *xattr_value = NULL;
> +	struct evm_xattr_config *config_data, *config_p;
> +	time_t xattr_refresh = 0;
> +
> +	if (!nd || !nd->dentry || !inode)
> +		return 0;
> +
> +	dentry = nd->dentry;
> +	if ((error = evm_valid_security(dentry, inode)) == 0)	/* Valid? */
> +		return 0;

And what if error != 0 here?

> +	isec = evm_get_isec(inode->i_security);
> +	if (!isec) {
> +		isec = evm_alloc_security();
> +		if (!isec)
> +			return (-ENOMEM);
> +		evm_set_isec(inode->i_security, isec);
> +	}

Race?  On entry, isec is NULL, and you go to allocate the security
structure, and another thread hits inode_permission on the same inode,
and it allocates its own security structure, and someone loses at
evm_set_isec time.   Note that evm_set_isec is defined to be
security_set_value_type(), and the stacker documentation notes that you
cannot use it safely outside of the object's alloc_security hook
function.

> +	/*Numerous reasons for no hmac, errors cached in istatus->xattr_flags.*/
> +	error = evm_verify_hmac(dentry, inode);
> +	if (error < 0)
> +		return 0;

I see the comment, but I'm not clear on why you think it is safe to return 0 here.

> diff -rpuN linux-2.6.14.2.orig/security/evm/evm_config.c linux-2.6.14.2.tc/security/evm/evm_config.c
> --- linux-2.6.14.2.orig/security/evm/evm_config.c	1969-12-31 19:00:00.000000000 -0500
> +++ linux-2.6.14.2.tc/security/evm/evm_config.c	2005-11-11 13:43:18.000000000 -0500
<snip>
> +int evm_get_config_info(struct evm_xattr_config **head, int *size,
> +			time_t * version)
> +{
> +	*head = evm_config_data;
> +	*size = evm_config_datasize;
> +	*version = evm_config_version;
> +	return 0;
> +}
> +
> +/*
> + * Initialize the Extended Verification module
> + */
> +int evm_update_config(struct evm_xattr_config *evm_new_data,
> +		      int evm_new_datasize)
> +{
<snip>
> +	if (evm_new_datasize > 0) {
> +		struct timespec now;
> +
> +		evm_old_data = evm_config_data;
> +		evm_old_datasize = evm_config_datasize;
> +
> +		evm_config_data = evm_new_data;
> +		evm_config_datasize = evm_new_datasize;
> +		now = CURRENT_TIME;
> +		evm_config_version = now.tv_sec;
> +
> +		evm_isec_size = sizeof(struct evm_isec_cache) +
> +		    ((evm_config_datasize + 1)
> +		     * sizeof(struct evm_isec_xattr));

What serializes multiple updates to the config via securityfs to prevent
clobbering one another, and what serializes the update with
evm_get_config_info to prevent the config from being visible in an
inconsistent state?

> diff -rpuN linux-2.6.14.2.orig/security/evm/evm_crypto.c linux-2.6.14.2.tc/security/evm/evm_crypto.c
> --- linux-2.6.14.2.orig/security/evm/evm_crypto.c	1969-12-31 19:00:00.000000000 -0500
> +++ linux-2.6.14.2.tc/security/evm/evm_crypto.c	2005-11-11 13:44:19.000000000 -0500
> +	crypto_digest_init(tfm);
> +	for (offset = 0; offset < file->f_dentry->d_inode->i_size;

What happens if i_size is changing while you are doing this processing?

> +/*
> + * Calculate the hmac, update it, and mark the cache dirty.
> + */
> +int evm_update_hmac(struct dentry *dentry, int flags)
> +{
> +	char hmacVal[SHA1_DIGEST_SIZE];
> +	int rc = -1;
> +
> +	if (!dentry || !dentry->d_inode || !dentry->d_inode->i_op
> +	    || !dentry->d_inode->i_op->setxattr)
> +		return rc;
> +
> +	memset(hmacVal, 0, SHA1_DIGEST_SIZE);
> +	if ((rc = evm_calc_hmac(dentry, hmacVal)) == 0) {
> +		struct evm_isec_cache *isec;
> +		struct inode *inode;
> +
> +		rc = dentry->d_inode->i_op->setxattr(dentry,
> +				"security.evm.hmac", hmacVal,
> +				SHA1_DIGEST_SIZE, flags);

What serializes such updates to the HMAC attribute?  Ditto for all of
the other attributes set by EVM.  Both in terms of multiple updates by
EVM itself and in terms of userspace-initiated updates that may end up
running concurrent with an EVM-initiated update.

> diff -rpuN linux-2.6.14.2.orig/security/evm/evm_init.c linux-2.6.14.2.tc/security/evm/evm_init.c
> --- linux-2.6.14.2.orig/security/evm/evm_init.c	1969-12-31 19:00:00.000000000 -0500
> +++ linux-2.6.14.2.tc/security/evm/evm_init.c	2005-11-11 13:45:38.000000000 -0500
<snip>
> +/*
> + * Rigid enforcement of signed executables,
> + *	 when a sandboxing module is not present.
> + */
> +static int evm_bprm_check_security(struct linux_binprm *bprm)

Not very rigid.  Look at digsig again and explain why you don't need
file_mmap/mprotect hooks.  Or look at SELinux (not signed executables in
that case, but access control is applied there).

> diff -rpuN linux-2.6.14.2.orig/security/evm/evm_xattr.c linux-2.6.14.2.tc/security/evm/evm_xattr.c
> --- linux-2.6.14.2.orig/security/evm/evm_xattr.c	1969-12-31 19:00:00.000000000 -0500
> +++ linux-2.6.14.2.tc/security/evm/evm_xattr.c	2005-11-11 13:46:26.000000000 -0500
> +int evm_inode_setxattr(struct dentry *dentry, char *name, void *value,
> +		       size_t size, int flags)
> +{
> +	int error = 0;
> +	struct evm_xattr_config *config_p;
> +
> +	struct timespec now;
> +	time_t secVal;
> +	const unsigned char *debug_name = "???";
> +	struct evm_xattr_config *config_data;
> +	int config_datasize = 0;	/* number of extended attributes */
> +	time_t config_version;
> +
> +	if (!dentry || !name || !value)
> +		return -EINVAL;
> +
> +	now = CURRENT_TIME;
> +	if (dentry && dentry->d_name.name)
> +		debug_name = dentry->d_name.name;
> +	evm_get_config_info(&config_data, &config_datasize,
> +			    &config_version);
> +	for_each_xattr(config_p, config_data, config_datasize) {
> +		if (memcmp(name, config_p->xattr_name, strlen(name)) == 0) {
> +			secVal = ntohl(*((time_t *) value));

What verifies that size >= sizeof(time_t)?  Alignment issue?

-- 
Stephen Smalley
National Security Agency



This archive was generated by hypermail 2.1.3 : Tue Nov 15 2005 - 10:03:07 PST