summaryrefslogtreecommitdiff
path: root/security/integrity/evm/evm_crypto.c
diff options
context:
space:
mode:
Diffstat (limited to 'security/integrity/evm/evm_crypto.c')
-rw-r--r--security/integrity/evm/evm_crypto.c279
1 files changed, 207 insertions, 72 deletions
diff --git a/security/integrity/evm/evm_crypto.c b/security/integrity/evm/evm_crypto.c
index 1d32cd20009a..a5e730ffda57 100644
--- a/security/integrity/evm/evm_crypto.c
+++ b/security/integrity/evm/evm_crypto.c
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2005-2010 IBM Corporation
*
@@ -5,31 +6,28 @@
* Mimi Zohar <zohar@us.ibm.com>
* Kylene Hall <kjhall@us.ibm.com>
*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 2 of the License.
- *
* File: evm_crypto.c
* Using root's kernel master key (kmk), calculate the HMAC
*/
-#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#define pr_fmt(fmt) "EVM: "fmt
-#include <linux/module.h>
+#include <linux/export.h>
#include <linux/crypto.h>
#include <linux/xattr.h>
#include <linux/evm.h>
#include <keys/encrypted-type.h>
#include <crypto/hash.h>
+#include <crypto/hash_info.h>
#include "evm.h"
#define EVMKEY "evm-key"
#define MAX_KEY_SIZE 128
static unsigned char evmkey[MAX_KEY_SIZE];
-static int evmkey_len = MAX_KEY_SIZE;
+static const int evmkey_len = MAX_KEY_SIZE;
-struct crypto_shash *hmac_tfm;
-struct crypto_shash *hash_tfm;
+static struct crypto_shash *hmac_tfm;
+static struct crypto_shash *evm_tfm[HASH_ALGO__LAST];
static DEFINE_MUTEX(mutex);
@@ -37,10 +35,12 @@ static DEFINE_MUTEX(mutex);
static unsigned long evm_set_key_flags;
+static const char evm_hmac[] = "hmac(sha1)";
+
/**
* evm_set_key() - set EVM HMAC key from the kernel
* @key: pointer to a buffer with the key data
- * @size: length of the key data
+ * @keylen: length of the key data
*
* This function allows setting the EVM HMAC key from the kernel
* without using the "encrypted" key subsystem keys. It can be used
@@ -71,57 +71,59 @@ busy:
}
EXPORT_SYMBOL_GPL(evm_set_key);
-static struct shash_desc *init_desc(char type)
+static struct shash_desc *init_desc(char type, uint8_t hash_algo)
{
long rc;
- char *algo;
- struct crypto_shash **tfm;
+ const char *algo;
+ struct crypto_shash **tfm, *tmp_tfm;
struct shash_desc *desc;
if (type == EVM_XATTR_HMAC) {
if (!(evm_initialized & EVM_INIT_HMAC)) {
- pr_err("HMAC key is not set\n");
+ pr_err_once("HMAC key is not set\n");
return ERR_PTR(-ENOKEY);
}
tfm = &hmac_tfm;
algo = evm_hmac;
} else {
- tfm = &hash_tfm;
- algo = evm_hash;
+ if (hash_algo >= HASH_ALGO__LAST)
+ return ERR_PTR(-EINVAL);
+
+ tfm = &evm_tfm[hash_algo];
+ algo = hash_algo_name[hash_algo];
}
- if (*tfm == NULL) {
- mutex_lock(&mutex);
- if (*tfm)
- goto out;
- *tfm = crypto_alloc_shash(algo, 0, CRYPTO_ALG_ASYNC);
- if (IS_ERR(*tfm)) {
- rc = PTR_ERR(*tfm);
- pr_err("Can not allocate %s (reason: %ld)\n", algo, rc);
- *tfm = NULL;
+ if (*tfm)
+ goto alloc;
+ mutex_lock(&mutex);
+ if (*tfm)
+ goto unlock;
+
+ tmp_tfm = crypto_alloc_shash(algo, 0, CRYPTO_NOLOAD);
+ if (IS_ERR(tmp_tfm)) {
+ pr_err("Can not allocate %s (reason: %ld)\n", algo,
+ PTR_ERR(tmp_tfm));
+ mutex_unlock(&mutex);
+ return ERR_CAST(tmp_tfm);
+ }
+ if (type == EVM_XATTR_HMAC) {
+ rc = crypto_shash_setkey(tmp_tfm, evmkey, evmkey_len);
+ if (rc) {
+ crypto_free_shash(tmp_tfm);
mutex_unlock(&mutex);
return ERR_PTR(rc);
}
- if (type == EVM_XATTR_HMAC) {
- rc = crypto_shash_setkey(*tfm, evmkey, evmkey_len);
- if (rc) {
- crypto_free_shash(*tfm);
- *tfm = NULL;
- mutex_unlock(&mutex);
- return ERR_PTR(rc);
- }
- }
-out:
- mutex_unlock(&mutex);
}
-
+ *tfm = tmp_tfm;
+unlock:
+ mutex_unlock(&mutex);
+alloc:
desc = kmalloc(sizeof(*desc) + crypto_shash_descsize(*tfm),
GFP_KERNEL);
if (!desc)
return ERR_PTR(-ENOMEM);
desc->tfm = *tfm;
- desc->flags = CRYPTO_TFM_REQ_MAY_SLEEP;
rc = crypto_shash_init(desc);
if (rc) {
@@ -138,7 +140,7 @@ out:
* protection.)
*/
static void hmac_add_misc(struct shash_desc *desc, struct inode *inode,
- char *digest)
+ char type, char *digest)
{
struct h_misc {
unsigned long ino;
@@ -149,8 +151,13 @@ static void hmac_add_misc(struct shash_desc *desc, struct inode *inode,
} hmac_misc;
memset(&hmac_misc, 0, sizeof(hmac_misc));
- hmac_misc.ino = inode->i_ino;
- hmac_misc.generation = inode->i_generation;
+ /* Don't include the inode or generation number in portable
+ * signatures
+ */
+ if (type != EVM_XATTR_PORTABLE_DIGSIG) {
+ hmac_misc.ino = inode->i_ino;
+ hmac_misc.generation = inode->i_generation;
+ }
/* The hmac uid and gid must be encoded in the initial user
* namespace (not the filesystems user namespace) as encoding
* them in the filesystems user namespace allows an attack
@@ -163,10 +170,44 @@ static void hmac_add_misc(struct shash_desc *desc, struct inode *inode,
hmac_misc.gid = from_kgid(&init_user_ns, inode->i_gid);
hmac_misc.mode = inode->i_mode;
crypto_shash_update(desc, (const u8 *)&hmac_misc, sizeof(hmac_misc));
- if (evm_hmac_attrs & EVM_ATTR_FSUUID)
- crypto_shash_update(desc, &inode->i_sb->s_uuid.b[0],
- sizeof(inode->i_sb->s_uuid));
+ if ((evm_hmac_attrs & EVM_ATTR_FSUUID) &&
+ type != EVM_XATTR_PORTABLE_DIGSIG)
+ crypto_shash_update(desc, (u8 *)&inode->i_sb->s_uuid, UUID_SIZE);
crypto_shash_final(desc, digest);
+
+ pr_debug("hmac_misc: (%zu) [%*phN]\n", sizeof(struct h_misc),
+ (int)sizeof(struct h_misc), &hmac_misc);
+}
+
+/*
+ * Dump large security xattr values as a continuous ascii hexadecimal string.
+ * (pr_debug is limited to 64 bytes.)
+ */
+static void dump_security_xattr_l(const char *prefix, const void *src,
+ size_t count)
+{
+#if defined(DEBUG) || defined(CONFIG_DYNAMIC_DEBUG)
+ char *asciihex, *p;
+
+ p = asciihex = kmalloc(count * 2 + 1, GFP_KERNEL);
+ if (!asciihex)
+ return;
+
+ p = bin2hex(p, src, count);
+ *p = 0;
+ pr_debug("%s: (%zu) %.*s\n", prefix, count, (int)count * 2, asciihex);
+ kfree(asciihex);
+#endif
+}
+
+static void dump_security_xattr(const char *name, const char *value,
+ size_t value_len)
+{
+ if (value_len < 64)
+ pr_debug("%s: (%zu) [%*phN]\n", name, value_len,
+ (int)value_len, value);
+ else
+ dump_security_xattr_l(name, value, value_len);
}
/*
@@ -177,36 +218,60 @@ static void hmac_add_misc(struct shash_desc *desc, struct inode *inode,
* each xattr, but attempt to re-use the previously allocated memory.
*/
static int evm_calc_hmac_or_hash(struct dentry *dentry,
- const char *req_xattr_name,
- const char *req_xattr_value,
- size_t req_xattr_value_len,
- char type, char *digest)
+ const char *req_xattr_name,
+ const char *req_xattr_value,
+ size_t req_xattr_value_len,
+ uint8_t type, struct evm_digest *data,
+ struct evm_iint_cache *iint)
{
- struct inode *inode = d_backing_inode(dentry);
+ struct inode *inode = d_inode(d_real(dentry, D_REAL_METADATA));
+ struct xattr_list *xattr;
struct shash_desc *desc;
- char **xattrname;
size_t xattr_size = 0;
char *xattr_value = NULL;
int error;
- int size;
+ int size, user_space_size;
+ bool ima_present = false;
+ u64 i_version = 0;
- if (!(inode->i_opflags & IOP_XATTR))
+ if (!(inode->i_opflags & IOP_XATTR) ||
+ inode->i_sb->s_user_ns != &init_user_ns)
return -EOPNOTSUPP;
- desc = init_desc(type);
+ desc = init_desc(type, data->hdr.algo);
if (IS_ERR(desc))
return PTR_ERR(desc);
+ data->hdr.length = crypto_shash_digestsize(desc->tfm);
+
error = -ENODATA;
- for (xattrname = evm_config_xattrnames; *xattrname != NULL; xattrname++) {
+ list_for_each_entry_lockless(xattr, &evm_config_xattrnames, list) {
+ bool is_ima = false;
+
+ if (strcmp(xattr->name, XATTR_NAME_IMA) == 0)
+ is_ima = true;
+
+ /*
+ * Skip non-enabled xattrs for locally calculated
+ * signatures/HMACs.
+ */
+ if (type != EVM_XATTR_PORTABLE_DIGSIG && !xattr->enabled)
+ continue;
+
if ((req_xattr_name && req_xattr_value)
- && !strcmp(*xattrname, req_xattr_name)) {
+ && !strcmp(xattr->name, req_xattr_name)) {
error = 0;
crypto_shash_update(desc, (const u8 *)req_xattr_value,
req_xattr_value_len);
+ if (is_ima)
+ ima_present = true;
+
+ dump_security_xattr(req_xattr_name,
+ req_xattr_value,
+ req_xattr_value_len);
continue;
}
- size = vfs_getxattr_alloc(dentry, *xattrname,
+ size = vfs_getxattr_alloc(&nop_mnt_idmap, dentry, xattr->name,
&xattr_value, xattr_size, GFP_NOFS);
if (size == -ENOMEM) {
error = -ENOMEM;
@@ -215,12 +280,32 @@ static int evm_calc_hmac_or_hash(struct dentry *dentry,
if (size < 0)
continue;
+ user_space_size = vfs_getxattr(&nop_mnt_idmap, dentry,
+ xattr->name, NULL, 0);
+ if (user_space_size != size)
+ pr_debug("file %s: xattr %s size mismatch (kernel: %d, user: %d)\n",
+ dentry->d_name.name, xattr->name, size,
+ user_space_size);
error = 0;
xattr_size = size;
crypto_shash_update(desc, (const u8 *)xattr_value, xattr_size);
+ if (is_ima)
+ ima_present = true;
+
+ dump_security_xattr(xattr->name, xattr_value, xattr_size);
}
- hmac_add_misc(desc, inode, digest);
+ hmac_add_misc(desc, inode, type, data->digest);
+ if (inode != d_backing_inode(dentry) && iint) {
+ if (IS_I_VERSION(inode))
+ i_version = inode_query_iversion(inode);
+ integrity_inode_attrs_store(&iint->metadata_inode, i_version,
+ inode);
+ }
+
+ /* Portable EVM signatures must include an IMA hash */
+ if (type == EVM_XATTR_PORTABLE_DIGSIG && !ima_present)
+ error = -EPERM;
out:
kfree(xattr_value);
kfree(desc);
@@ -229,20 +314,50 @@ out:
int evm_calc_hmac(struct dentry *dentry, const char *req_xattr_name,
const char *req_xattr_value, size_t req_xattr_value_len,
- char *digest)
+ struct evm_digest *data, struct evm_iint_cache *iint)
{
return evm_calc_hmac_or_hash(dentry, req_xattr_name, req_xattr_value,
- req_xattr_value_len, EVM_XATTR_HMAC, digest);
+ req_xattr_value_len, EVM_XATTR_HMAC, data,
+ iint);
}
int evm_calc_hash(struct dentry *dentry, const char *req_xattr_name,
const char *req_xattr_value, size_t req_xattr_value_len,
- char *digest)
+ char type, struct evm_digest *data, struct evm_iint_cache *iint)
{
return evm_calc_hmac_or_hash(dentry, req_xattr_name, req_xattr_value,
- req_xattr_value_len, IMA_XATTR_DIGEST, digest);
+ req_xattr_value_len, type, data, iint);
+}
+
+static int evm_is_immutable(struct dentry *dentry, struct inode *inode)
+{
+ const struct evm_ima_xattr_data *xattr_data = NULL;
+ struct evm_iint_cache *iint;
+ int rc = 0;
+
+ iint = evm_iint_inode(inode);
+ if (iint && (iint->flags & EVM_IMMUTABLE_DIGSIG))
+ return 1;
+
+ /* Do this the hard way */
+ rc = vfs_getxattr_alloc(&nop_mnt_idmap, dentry, XATTR_NAME_EVM,
+ (char **)&xattr_data, 0, GFP_NOFS);
+ if (rc <= 0) {
+ if (rc == -ENODATA)
+ rc = 0;
+ goto out;
+ }
+ if (xattr_data->type == EVM_XATTR_PORTABLE_DIGSIG)
+ rc = 1;
+ else
+ rc = 0;
+
+out:
+ kfree(xattr_data);
+ return rc;
}
+
/*
* Calculate the hmac and update security.evm xattr
*
@@ -252,35 +367,55 @@ int evm_update_evmxattr(struct dentry *dentry, const char *xattr_name,
const char *xattr_value, size_t xattr_value_len)
{
struct inode *inode = d_backing_inode(dentry);
- struct evm_ima_xattr_data xattr_data;
+ struct evm_iint_cache *iint = evm_iint_inode(inode);
+ struct evm_digest data;
int rc = 0;
+ /*
+ * Don't permit any transformation of the EVM xattr if the signature
+ * is of an immutable type
+ */
+ rc = evm_is_immutable(dentry, inode);
+ if (rc < 0)
+ return rc;
+ if (rc)
+ return -EPERM;
+
+ data.hdr.algo = HASH_ALGO_SHA1;
rc = evm_calc_hmac(dentry, xattr_name, xattr_value,
- xattr_value_len, xattr_data.digest);
+ xattr_value_len, &data, iint);
if (rc == 0) {
- xattr_data.type = EVM_XATTR_HMAC;
- rc = __vfs_setxattr_noperm(dentry, XATTR_NAME_EVM,
- &xattr_data,
- sizeof(xattr_data), 0);
+ data.hdr.xattr.sha1.type = EVM_XATTR_HMAC;
+ rc = __vfs_setxattr_noperm(&nop_mnt_idmap, dentry,
+ XATTR_NAME_EVM,
+ &data.hdr.xattr.data[1],
+ SHA1_DIGEST_SIZE + 1, 0);
} else if (rc == -ENODATA && (inode->i_opflags & IOP_XATTR)) {
- rc = __vfs_removexattr(dentry, XATTR_NAME_EVM);
+ rc = __vfs_removexattr(&nop_mnt_idmap, dentry, XATTR_NAME_EVM);
}
return rc;
}
-int evm_init_hmac(struct inode *inode, const struct xattr *lsm_xattr,
+int evm_init_hmac(struct inode *inode, const struct xattr *xattrs,
char *hmac_val)
{
struct shash_desc *desc;
+ const struct xattr *xattr;
- desc = init_desc(EVM_XATTR_HMAC);
+ desc = init_desc(EVM_XATTR_HMAC, HASH_ALGO_SHA1);
if (IS_ERR(desc)) {
pr_info("init_desc failed\n");
return PTR_ERR(desc);
}
- crypto_shash_update(desc, lsm_xattr->value, lsm_xattr->value_len);
- hmac_add_misc(desc, inode, hmac_val);
+ for (xattr = xattrs; xattr->name; xattr++) {
+ if (!evm_protected_xattr(xattr->name))
+ continue;
+
+ crypto_shash_update(desc, xattr->value, xattr->value_len);
+ }
+
+ hmac_add_misc(desc, inode, EVM_XATTR_HMAC, hmac_val);
kfree(desc);
return 0;
}