From b7e27bc1d42e8e0cc58b602b529c25cd0071b336 Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Wed, 8 Nov 2017 07:38:28 -0500 Subject: [PATCH 01/11] ima: relax requiring a file signature for new files with zero length Custom policies can require file signatures based on LSM labels. These files are normally created and only afterwards labeled, requiring them to be signed. Instead of requiring file signatures based on LSM labels, entire filesystems could require file signatures. In this case, we need the ability of writing new files without requiring file signatures. The definition of a "new" file was originally defined as any file with a length of zero. Subsequent patches redefined a "new" file to be based on the FILE_CREATE open flag. By combining the open flag with a file size of zero, this patch relaxes the file signature requirement. Fixes: 1ac202e978e1 ima: accept previously set IMA_NEW_FILE Signed-off-by: Mimi Zohar --- security/integrity/ima/ima_appraise.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index 65fbcf3c32c7..d32e6a1d931a 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -223,7 +223,8 @@ int ima_appraise_measurement(enum ima_hooks func, if (opened & FILE_CREATED) iint->flags |= IMA_NEW_FILE; if ((iint->flags & IMA_NEW_FILE) && - !(iint->flags & IMA_DIGSIG_REQUIRED)) + (!(iint->flags & IMA_DIGSIG_REQUIRED) || + (inode->i_size == 0))) status = INTEGRITY_PASS; goto out; } From ae1ba1676b88e6c62368a433c7e2d0417e9879fd Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Tue, 7 Nov 2017 07:18:35 -0800 Subject: [PATCH 02/11] EVM: Allow userland to permit modification of EVM-protected metadata When EVM is enabled it forbids modification of metadata protected by EVM unless there is already a valid EVM signature. If any modification is made, the kernel will then generate a new EVM HMAC. However, this does not map well on use cases which use only asymmetric EVM signatures, as in this scenario the kernel is unable to generate new signatures. This patch extends the /sys/kernel/security/evm interface to allow userland to request that modification of these xattrs be permitted. This is only permitted if no keys have already been loaded. In this configuration, modifying the metadata will invalidate the EVM appraisal on the file in question. This allows packaging systems to write out new files, set the relevant extended attributes and then move them into place. There's also some refactoring of the use of evm_initialized in order to avoid heading down codepaths that assume there's a key available. Signed-off-by: Matthew Garrett Signed-off-by: Mimi Zohar --- Documentation/ABI/testing/evm | 54 +++++++++++++++++++----------- security/integrity/evm/evm.h | 7 ++-- security/integrity/evm/evm_main.c | 38 +++++++++++++++++---- security/integrity/evm/evm_secfs.c | 20 +++++++++-- 4 files changed, 88 insertions(+), 31 deletions(-) diff --git a/Documentation/ABI/testing/evm b/Documentation/ABI/testing/evm index 9578247e1792..d12cb2eae9ee 100644 --- a/Documentation/ABI/testing/evm +++ b/Documentation/ABI/testing/evm @@ -14,30 +14,46 @@ Description: generated either locally or remotely using an asymmetric key. These keys are loaded onto root's keyring using keyctl, and EVM is then enabled by - echoing a value to /evm: + echoing a value to /evm made up of the + following bits: - 1: enable HMAC validation and creation - 2: enable digital signature validation - 3: enable HMAC and digital signature validation and HMAC - creation + Bit Effect + 0 Enable HMAC validation and creation + 1 Enable digital signature validation + 2 Permit modification of EVM-protected metadata at + runtime. Not supported if HMAC validation and + creation is enabled. + 31 Disable further runtime modification of EVM policy - Further writes will be blocked if HMAC support is enabled or - if bit 32 is set: + For example: - echo 0x80000002 >/evm + echo 1 >/evm - will enable digital signature validation and block - further writes to /evm. + will enable HMAC validation and creation - Until this is done, EVM can not create or validate the - 'security.evm' xattr, but returns INTEGRITY_UNKNOWN. - Loading keys and signaling EVM should be done as early - as possible. Normally this is done in the initramfs, - which has already been measured as part of the trusted - boot. For more information on creating and loading - existing trusted/encrypted keys, refer to: + echo 0x80000003 >/evm - Documentation/security/keys/trusted-encrypted.rst. Both dracut - (via 97masterkey and 98integrity) and systemd (via + will enable HMAC and digital signature validation and + HMAC creation and disable all further modification of policy. + + echo 0x80000006 >/evm + + will enable digital signature validation, permit + modification of EVM-protected metadata and + disable all further modification of policy + + Note that once a key has been loaded, it will no longer be + possible to enable metadata modification. + + Until key loading has been signaled EVM can not create + or validate the 'security.evm' xattr, but returns + INTEGRITY_UNKNOWN. Loading keys and signaling EVM + should be done as early as possible. Normally this is + done in the initramfs, which has already been measured + as part of the trusted boot. For more information on + creating and loading existing trusted/encrypted keys, + refer to: + Documentation/security/keys/trusted-encrypted.rst. Both + dracut (via 97masterkey and 98integrity) and systemd (via core/ima-setup) have support for loading keys at boot time. diff --git a/security/integrity/evm/evm.h b/security/integrity/evm/evm.h index 241aca315b0c..3d05250e8313 100644 --- a/security/integrity/evm/evm.h +++ b/security/integrity/evm/evm.h @@ -23,9 +23,12 @@ #define EVM_INIT_HMAC 0x0001 #define EVM_INIT_X509 0x0002 -#define EVM_SETUP 0x80000000 /* userland has signaled key load */ +#define EVM_ALLOW_METADATA_WRITES 0x0004 +#define EVM_SETUP_COMPLETE 0x80000000 /* userland has signaled key load */ -#define EVM_INIT_MASK (EVM_INIT_HMAC | EVM_INIT_X509 | EVM_SETUP) +#define EVM_KEY_MASK (EVM_INIT_HMAC | EVM_INIT_X509) +#define EVM_INIT_MASK (EVM_INIT_HMAC | EVM_INIT_X509 | EVM_SETUP_COMPLETE | \ + EVM_ALLOW_METADATA_WRITES) extern int evm_initialized; extern char *evm_hmac; diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c index 9826c02e2db8..ba89c2468298 100644 --- a/security/integrity/evm/evm_main.c +++ b/security/integrity/evm/evm_main.c @@ -76,6 +76,11 @@ static void __init evm_init_config(void) pr_info("HMAC attrs: 0x%x\n", evm_hmac_attrs); } +static bool evm_key_loaded(void) +{ + return (bool)(evm_initialized & EVM_KEY_MASK); +} + static int evm_find_protected_xattrs(struct dentry *dentry) { struct inode *inode = d_backing_inode(dentry); @@ -241,7 +246,7 @@ enum integrity_status evm_verifyxattr(struct dentry *dentry, void *xattr_value, size_t xattr_value_len, struct integrity_iint_cache *iint) { - if (!evm_initialized || !evm_protected_xattr(xattr_name)) + if (!evm_key_loaded() || !evm_protected_xattr(xattr_name)) return INTEGRITY_UNKNOWN; if (!iint) { @@ -265,7 +270,7 @@ static enum integrity_status evm_verify_current_integrity(struct dentry *dentry) { struct inode *inode = d_backing_inode(dentry); - if (!evm_initialized || !S_ISREG(inode->i_mode) || evm_fixmode) + if (!evm_key_loaded() || !S_ISREG(inode->i_mode) || evm_fixmode) return 0; return evm_verify_hmac(dentry, NULL, NULL, 0, NULL); } @@ -299,6 +304,7 @@ static int evm_protect_xattr(struct dentry *dentry, const char *xattr_name, return 0; goto out; } + evm_status = evm_verify_current_integrity(dentry); if (evm_status == INTEGRITY_NOXATTRS) { struct integrity_iint_cache *iint; @@ -345,6 +351,12 @@ int evm_inode_setxattr(struct dentry *dentry, const char *xattr_name, { const struct evm_ima_xattr_data *xattr_data = xattr_value; + /* Policy permits modification of the protected xattrs even though + * there's no HMAC key loaded + */ + if (evm_initialized & EVM_ALLOW_METADATA_WRITES) + return 0; + if (strcmp(xattr_name, XATTR_NAME_EVM) == 0) { if (!xattr_value_len) return -EINVAL; @@ -365,6 +377,12 @@ int evm_inode_setxattr(struct dentry *dentry, const char *xattr_name, */ int evm_inode_removexattr(struct dentry *dentry, const char *xattr_name) { + /* Policy permits modification of the protected xattrs even though + * there's no HMAC key loaded + */ + if (evm_initialized & EVM_ALLOW_METADATA_WRITES) + return 0; + return evm_protect_xattr(dentry, xattr_name, NULL, 0); } @@ -393,8 +411,8 @@ static void evm_reset_status(struct inode *inode) void evm_inode_post_setxattr(struct dentry *dentry, const char *xattr_name, const void *xattr_value, size_t xattr_value_len) { - if (!evm_initialized || (!evm_protected_xattr(xattr_name) - && !posix_xattr_acl(xattr_name))) + if (!evm_key_loaded() || (!evm_protected_xattr(xattr_name) + && !posix_xattr_acl(xattr_name))) return; evm_reset_status(dentry->d_inode); @@ -414,7 +432,7 @@ void evm_inode_post_setxattr(struct dentry *dentry, const char *xattr_name, */ void evm_inode_post_removexattr(struct dentry *dentry, const char *xattr_name) { - if (!evm_initialized || !evm_protected_xattr(xattr_name)) + if (!evm_key_loaded() || !evm_protected_xattr(xattr_name)) return; evm_reset_status(dentry->d_inode); @@ -431,6 +449,12 @@ int evm_inode_setattr(struct dentry *dentry, struct iattr *attr) unsigned int ia_valid = attr->ia_valid; enum integrity_status evm_status; + /* Policy permits modification of the protected attrs even though + * there's no HMAC key loaded + */ + if (evm_initialized & EVM_ALLOW_METADATA_WRITES) + return 0; + if (!(ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID))) return 0; evm_status = evm_verify_current_integrity(dentry); @@ -456,7 +480,7 @@ int evm_inode_setattr(struct dentry *dentry, struct iattr *attr) */ void evm_inode_post_setattr(struct dentry *dentry, int ia_valid) { - if (!evm_initialized) + if (!evm_key_loaded()) return; if (ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID)) @@ -473,7 +497,7 @@ int evm_inode_init_security(struct inode *inode, struct evm_ima_xattr_data *xattr_data; int rc; - if (!evm_initialized || !evm_protected_xattr(lsm_xattr->name)) + if (!evm_key_loaded() || !evm_protected_xattr(lsm_xattr->name)) return 0; xattr_data = kzalloc(sizeof(*xattr_data), GFP_NOFS); diff --git a/security/integrity/evm/evm_secfs.c b/security/integrity/evm/evm_secfs.c index 319cf16d6603..feba03bbedae 100644 --- a/security/integrity/evm/evm_secfs.c +++ b/security/integrity/evm/evm_secfs.c @@ -40,7 +40,7 @@ static ssize_t evm_read_key(struct file *filp, char __user *buf, if (*ppos != 0) return 0; - sprintf(temp, "%d", (evm_initialized & ~EVM_SETUP)); + sprintf(temp, "%d", (evm_initialized & ~EVM_SETUP_COMPLETE)); rc = simple_read_from_buffer(buf, count, ppos, temp, strlen(temp)); return rc; @@ -63,7 +63,7 @@ static ssize_t evm_write_key(struct file *file, const char __user *buf, { int i, ret; - if (!capable(CAP_SYS_ADMIN) || (evm_initialized & EVM_SETUP)) + if (!capable(CAP_SYS_ADMIN) || (evm_initialized & EVM_SETUP_COMPLETE)) return -EPERM; ret = kstrtoint_from_user(buf, count, 0, &i); @@ -75,16 +75,30 @@ static ssize_t evm_write_key(struct file *file, const char __user *buf, if (!i || (i & ~EVM_INIT_MASK) != 0) return -EINVAL; + /* Don't allow a request to freshly enable metadata writes if + * keys are loaded. + */ + if ((i & EVM_ALLOW_METADATA_WRITES) && + ((evm_initialized & EVM_KEY_MASK) != 0) && + !(evm_initialized & EVM_ALLOW_METADATA_WRITES)) + return -EPERM; + if (i & EVM_INIT_HMAC) { ret = evm_init_key(); if (ret != 0) return ret; /* Forbid further writes after the symmetric key is loaded */ - i |= EVM_SETUP; + i |= EVM_SETUP_COMPLETE; } evm_initialized |= i; + /* Don't allow protected metadata modification if a symmetric key + * is loaded + */ + if (evm_initialized & EVM_INIT_HMAC) + evm_initialized &= ~(EVM_ALLOW_METADATA_WRITES); + return count; } From 50b977481fce90aa5fbda55e330b9d722733e358 Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Tue, 7 Nov 2017 07:17:42 -0800 Subject: [PATCH 03/11] EVM: Add support for portable signature format The EVM signature includes the inode number and (optionally) the filesystem UUID, making it impractical to ship EVM signatures in packages. This patch adds a new portable format intended to allow distributions to include EVM signatures. It is identical to the existing format but hardcodes the inode and generation numbers to 0 and does not include the filesystem UUID even if the kernel is configured to do so. Removing the inode means that the metadata and signature from one file could be copied to another file without invalidating it. This is avoided by ensuring that an IMA xattr is present during EVM validation. Portable signatures are intended to be immutable - ie, they will never be transformed into HMACs. Based on earlier work by Dmitry Kasatkin and Mikhail Kurinnoi. Signed-off-by: Matthew Garrett Cc: Dmitry Kasatkin Cc: Mikhail Kurinnoi Signed-off-by: Mimi Zohar --- include/linux/integrity.h | 1 + security/integrity/evm/evm.h | 2 +- security/integrity/evm/evm_crypto.c | 75 +++++++++++++++++++++++---- security/integrity/evm/evm_main.c | 29 +++++++---- security/integrity/ima/ima_appraise.c | 4 +- security/integrity/integrity.h | 2 + 6 files changed, 92 insertions(+), 21 deletions(-) diff --git a/include/linux/integrity.h b/include/linux/integrity.h index c2d6082a1a4c..858d3f4a2241 100644 --- a/include/linux/integrity.h +++ b/include/linux/integrity.h @@ -14,6 +14,7 @@ enum integrity_status { INTEGRITY_PASS = 0, + INTEGRITY_PASS_IMMUTABLE, INTEGRITY_FAIL, INTEGRITY_NOLABEL, INTEGRITY_NOXATTRS, diff --git a/security/integrity/evm/evm.h b/security/integrity/evm/evm.h index 3d05250e8313..04825393facb 100644 --- a/security/integrity/evm/evm.h +++ b/security/integrity/evm/evm.h @@ -54,7 +54,7 @@ int evm_calc_hmac(struct dentry *dentry, const char *req_xattr_name, size_t req_xattr_value_len, char *digest); 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); + size_t req_xattr_value_len, char type, char *digest); int evm_init_hmac(struct inode *inode, const struct xattr *xattr, char *hmac_val); int evm_init_secfs(void); diff --git a/security/integrity/evm/evm_crypto.c b/security/integrity/evm/evm_crypto.c index bcd64baf8788..691f3e09154c 100644 --- a/security/integrity/evm/evm_crypto.c +++ b/security/integrity/evm/evm_crypto.c @@ -138,7 +138,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 +149,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,7 +168,8 @@ 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) + if ((evm_hmac_attrs & EVM_ATTR_FSUUID) && + type != EVM_XATTR_PORTABLE_DIGSIG) crypto_shash_update(desc, &inode->i_sb->s_uuid.b[0], sizeof(inode->i_sb->s_uuid)); crypto_shash_final(desc, digest); @@ -189,6 +195,7 @@ static int evm_calc_hmac_or_hash(struct dentry *dentry, char *xattr_value = NULL; int error; int size; + bool ima_present = false; if (!(inode->i_opflags & IOP_XATTR)) return -EOPNOTSUPP; @@ -199,11 +206,18 @@ static int evm_calc_hmac_or_hash(struct dentry *dentry, error = -ENODATA; for (xattrname = evm_config_xattrnames; *xattrname != NULL; xattrname++) { + bool is_ima = false; + + if (strcmp(*xattrname, XATTR_NAME_IMA) == 0) + is_ima = true; + if ((req_xattr_name && req_xattr_value) && !strcmp(*xattrname, req_xattr_name)) { error = 0; crypto_shash_update(desc, (const u8 *)req_xattr_value, req_xattr_value_len); + if (is_ima) + ima_present = true; continue; } size = vfs_getxattr_alloc(dentry, *xattrname, @@ -218,9 +232,14 @@ static int evm_calc_hmac_or_hash(struct dentry *dentry, error = 0; xattr_size = size; crypto_shash_update(desc, (const u8 *)xattr_value, xattr_size); + if (is_ima) + ima_present = true; } - hmac_add_misc(desc, inode, digest); + hmac_add_misc(desc, inode, type, digest); + /* Portable EVM signatures must include an IMA hash */ + if (type == EVM_XATTR_PORTABLE_DIGSIG && !ima_present) + return -EPERM; out: kfree(xattr_value); kfree(desc); @@ -232,17 +251,45 @@ int evm_calc_hmac(struct dentry *dentry, const char *req_xattr_name, char *digest) { 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, digest); } 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, char *digest) { 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, digest); } +static int evm_is_immutable(struct dentry *dentry, struct inode *inode) +{ + const struct evm_ima_xattr_data *xattr_data = NULL; + struct integrity_iint_cache *iint; + int rc = 0; + + iint = integrity_iint_find(inode); + if (iint && (iint->flags & EVM_IMMUTABLE_DIGSIG)) + return 1; + + /* Do this the hard way */ + rc = vfs_getxattr_alloc(dentry, XATTR_NAME_EVM, (char **)&xattr_data, 0, + GFP_NOFS); + if (rc <= 0) { + if (rc == -ENODATA) + return 0; + return rc; + } + if (xattr_data->type == EVM_XATTR_PORTABLE_DIGSIG) + rc = 1; + else + rc = 0; + + kfree(xattr_data); + return rc; +} + + /* * Calculate the hmac and update security.evm xattr * @@ -255,6 +302,16 @@ int evm_update_evmxattr(struct dentry *dentry, const char *xattr_name, struct evm_ima_xattr_data xattr_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; + rc = evm_calc_hmac(dentry, xattr_name, xattr_value, xattr_value_len, xattr_data.digest); if (rc == 0) { @@ -280,7 +337,7 @@ int evm_init_hmac(struct inode *inode, const struct xattr *lsm_xattr, } crypto_shash_update(desc, lsm_xattr->value, lsm_xattr->value_len); - hmac_add_misc(desc, inode, hmac_val); + hmac_add_misc(desc, inode, EVM_XATTR_HMAC, hmac_val); kfree(desc); return 0; } diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c index ba89c2468298..a8d502827270 100644 --- a/security/integrity/evm/evm_main.c +++ b/security/integrity/evm/evm_main.c @@ -31,7 +31,7 @@ int evm_initialized; static char *integrity_status_msg[] = { - "pass", "fail", "no_label", "no_xattrs", "unknown" + "pass", "pass_immutable", "fail", "no_label", "no_xattrs", "unknown" }; char *evm_hmac = "hmac(sha1)"; char *evm_hash = "sha1"; @@ -128,7 +128,8 @@ static enum integrity_status evm_verify_hmac(struct dentry *dentry, enum integrity_status evm_status = INTEGRITY_PASS; int rc, xattr_len; - if (iint && iint->evm_status == INTEGRITY_PASS) + if (iint && (iint->evm_status == INTEGRITY_PASS || + iint->evm_status == INTEGRITY_PASS_IMMUTABLE)) return iint->evm_status; /* if status is not PASS, try to check again - against -ENOMEM */ @@ -169,22 +170,26 @@ static enum integrity_status evm_verify_hmac(struct dentry *dentry, rc = -EINVAL; break; case EVM_IMA_XATTR_DIGSIG: + case EVM_XATTR_PORTABLE_DIGSIG: rc = evm_calc_hash(dentry, xattr_name, xattr_value, - xattr_value_len, calc.digest); + xattr_value_len, xattr_data->type, + calc.digest); if (rc) break; rc = integrity_digsig_verify(INTEGRITY_KEYRING_EVM, (const char *)xattr_data, xattr_len, calc.digest, sizeof(calc.digest)); if (!rc) { - /* Replace RSA with HMAC if not mounted readonly and - * not immutable - */ - if (!IS_RDONLY(d_backing_inode(dentry)) && - !IS_IMMUTABLE(d_backing_inode(dentry))) + if (xattr_data->type == EVM_XATTR_PORTABLE_DIGSIG) { + if (iint) + iint->flags |= EVM_IMMUTABLE_DIGSIG; + evm_status = INTEGRITY_PASS_IMMUTABLE; + } else if (!IS_RDONLY(d_backing_inode(dentry)) && + !IS_IMMUTABLE(d_backing_inode(dentry))) { evm_update_evmxattr(dentry, xattr_name, xattr_value, xattr_value_len); + } } break; default: @@ -285,7 +290,7 @@ static enum integrity_status evm_verify_current_integrity(struct dentry *dentry) * affect security.evm. An interesting side affect of writing posix xattr * acls is their modifying of the i_mode, which is included in security.evm. * For posix xattr acls only, permit security.evm, even if it currently - * doesn't exist, to be updated. + * doesn't exist, to be updated unless the EVM signature is immutable. */ static int evm_protect_xattr(struct dentry *dentry, const char *xattr_name, const void *xattr_value, size_t xattr_value_len) @@ -360,7 +365,8 @@ int evm_inode_setxattr(struct dentry *dentry, const char *xattr_name, if (strcmp(xattr_name, XATTR_NAME_EVM) == 0) { if (!xattr_value_len) return -EINVAL; - if (xattr_data->type != EVM_IMA_XATTR_DIGSIG) + if (xattr_data->type != EVM_IMA_XATTR_DIGSIG && + xattr_data->type != EVM_XATTR_PORTABLE_DIGSIG) return -EPERM; } return evm_protect_xattr(dentry, xattr_name, xattr_value, @@ -443,6 +449,9 @@ void evm_inode_post_removexattr(struct dentry *dentry, const char *xattr_name) /** * evm_inode_setattr - prevent updating an invalid EVM extended attribute * @dentry: pointer to the affected dentry + * + * Permit update of file attributes when files have a valid EVM signature, + * except in the case of them having an immutable portable signature. */ int evm_inode_setattr(struct dentry *dentry, struct iattr *attr) { diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index d32e6a1d931a..084c19e45668 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -230,7 +230,9 @@ int ima_appraise_measurement(enum ima_hooks func, } status = evm_verifyxattr(dentry, XATTR_NAME_IMA, xattr_value, rc, iint); - if ((status != INTEGRITY_PASS) && (status != INTEGRITY_UNKNOWN)) { + if ((status != INTEGRITY_PASS) && + (status != INTEGRITY_PASS_IMMUTABLE) && + (status != INTEGRITY_UNKNOWN)) { if ((status == INTEGRITY_NOLABEL) || (status == INTEGRITY_NOXATTRS)) cause = "missing-HMAC"; diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index e1bf040fb110..e324bf98c856 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -33,6 +33,7 @@ #define IMA_DIGSIG_REQUIRED 0x02000000 #define IMA_PERMIT_DIRECTIO 0x04000000 #define IMA_NEW_FILE 0x08000000 +#define EVM_IMMUTABLE_DIGSIG 0x10000000 #define IMA_DO_MASK (IMA_MEASURE | IMA_APPRAISE | IMA_AUDIT | \ IMA_APPRAISE_SUBMASK) @@ -58,6 +59,7 @@ enum evm_ima_xattr_type { EVM_XATTR_HMAC, EVM_IMA_XATTR_DIGSIG, IMA_XATTR_DIGEST_NG, + EVM_XATTR_PORTABLE_DIGSIG, IMA_XATTR_LAST }; From 0d73a55208e94fc9fb6deaeea61438cd3280d4c0 Mon Sep 17 00:00:00 2001 From: Dmitry Kasatkin Date: Tue, 5 Dec 2017 21:06:34 +0200 Subject: [PATCH 04/11] ima: re-introduce own integrity cache lock Before IMA appraisal was introduced, IMA was using own integrity cache lock along with i_mutex. process_measurement and ima_file_free took the iint->mutex first and then the i_mutex, while setxattr, chmod and chown took the locks in reverse order. To resolve the potential deadlock, i_mutex was moved to protect entire IMA functionality and the redundant iint->mutex was eliminated. Solution was based on the assumption that filesystem code does not take i_mutex further. But when file is opened with O_DIRECT flag, direct-io implementation takes i_mutex and produces deadlock. Furthermore, certain other filesystem operations, such as llseek, also take i_mutex. More recently some filesystems have replaced their filesystem specific lock with the global i_rwsem to read a file. As a result, when IMA attempts to calculate the file hash, reading the file attempts to take the i_rwsem again. To resolve O_DIRECT related deadlock problem, this patch re-introduces iint->mutex. But to eliminate the original chmod() related deadlock problem, this patch eliminates the requirement for chmod hooks to take the iint->mutex by introducing additional atomic iint->attr_flags to indicate calling of the hooks. The allowed locking order is to take the iint->mutex first and then the i_rwsem. Original flags were cleared in chmod(), setxattr() or removwxattr() hooks and tested when file was closed or opened again. New atomic flags are set or cleared in those hooks and tested to clear iint->flags on close or on open. Atomic flags are following: * IMA_CHANGE_ATTR - indicates that chATTR() was called (chmod, chown, chgrp) and file attributes have changed. On file open, it causes IMA to clear iint->flags to re-evaluate policy and perform IMA functions again. * IMA_CHANGE_XATTR - indicates that setxattr or removexattr was called and extended attributes have changed. On file open, it causes IMA to clear iint->flags IMA_DONE_MASK to re-appraise. * IMA_UPDATE_XATTR - indicates that security.ima needs to be updated. It is cleared if file policy changes and no update is needed. * IMA_DIGSIG - indicates that file security.ima has signature and file security.ima must not update to file has on file close. * IMA_MUST_MEASURE - indicates the file is in the measurement policy. Fixes: Commit 6552321831dc ("xfs: remove i_iolock and use i_rwsem in the VFS inode instead") Signed-off-by: Dmitry Kasatkin Signed-off-by: Mimi Zohar --- security/integrity/iint.c | 2 + security/integrity/ima/ima_appraise.c | 29 +++++------ security/integrity/ima/ima_main.c | 70 ++++++++++++++++++--------- security/integrity/integrity.h | 18 +++++-- 4 files changed, 78 insertions(+), 41 deletions(-) diff --git a/security/integrity/iint.c b/security/integrity/iint.c index c84e05866052..d726ba23a178 100644 --- a/security/integrity/iint.c +++ b/security/integrity/iint.c @@ -155,12 +155,14 @@ static void init_once(void *foo) memset(iint, 0, sizeof(*iint)); iint->version = 0; iint->flags = 0UL; + iint->atomic_flags = 0; iint->ima_file_status = INTEGRITY_UNKNOWN; iint->ima_mmap_status = INTEGRITY_UNKNOWN; iint->ima_bprm_status = INTEGRITY_UNKNOWN; iint->ima_read_status = INTEGRITY_UNKNOWN; iint->evm_status = INTEGRITY_UNKNOWN; iint->measured_pcrs = 0; + mutex_init(&iint->mutex); } static int __init integrity_iintcache_init(void) diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index 084c19e45668..ea1245606a14 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -251,6 +251,7 @@ int ima_appraise_measurement(enum ima_hooks func, status = INTEGRITY_FAIL; break; } + clear_bit(IMA_DIGSIG, &iint->atomic_flags); if (xattr_len - sizeof(xattr_value->type) - hash_start >= iint->ima_hash->length) /* xattr length may be longer. md5 hash in previous @@ -269,7 +270,7 @@ int ima_appraise_measurement(enum ima_hooks func, status = INTEGRITY_PASS; break; case EVM_IMA_XATTR_DIGSIG: - iint->flags |= IMA_DIGSIG; + set_bit(IMA_DIGSIG, &iint->atomic_flags); rc = integrity_digsig_verify(INTEGRITY_KEYRING_IMA, (const char *)xattr_value, rc, iint->ima_hash->digest, @@ -320,7 +321,7 @@ void ima_update_xattr(struct integrity_iint_cache *iint, struct file *file) int rc = 0; /* do not collect and update hash for digital signatures */ - if (iint->flags & IMA_DIGSIG) + if (test_bit(IMA_DIGSIG, &iint->atomic_flags)) return; if (iint->ima_file_status != INTEGRITY_PASS) @@ -330,7 +331,9 @@ void ima_update_xattr(struct integrity_iint_cache *iint, struct file *file) if (rc < 0) return; + inode_lock(file_inode(file)); ima_fix_xattr(dentry, iint); + inode_unlock(file_inode(file)); } /** @@ -353,16 +356,14 @@ void ima_inode_post_setattr(struct dentry *dentry) return; must_appraise = ima_must_appraise(inode, MAY_ACCESS, POST_SETATTR); - iint = integrity_iint_find(inode); - if (iint) { - iint->flags &= ~(IMA_APPRAISE | IMA_APPRAISED | - IMA_APPRAISE_SUBMASK | IMA_APPRAISED_SUBMASK | - IMA_ACTION_RULE_FLAGS); - if (must_appraise) - iint->flags |= IMA_APPRAISE; - } if (!must_appraise) __vfs_removexattr(dentry, XATTR_NAME_IMA); + iint = integrity_iint_find(inode); + if (iint) { + set_bit(IMA_CHANGE_ATTR, &iint->atomic_flags); + if (!must_appraise) + clear_bit(IMA_UPDATE_XATTR, &iint->atomic_flags); + } } /* @@ -391,12 +392,12 @@ static void ima_reset_appraise_flags(struct inode *inode, int digsig) iint = integrity_iint_find(inode); if (!iint) return; - - iint->flags &= ~IMA_DONE_MASK; iint->measured_pcrs = 0; + set_bit(IMA_CHANGE_XATTR, &iint->atomic_flags); if (digsig) - iint->flags |= IMA_DIGSIG; - return; + set_bit(IMA_DIGSIG, &iint->atomic_flags); + else + clear_bit(IMA_DIGSIG, &iint->atomic_flags); } int ima_inode_setxattr(struct dentry *dentry, const char *xattr_name, diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 770654694efc..edf4e0717494 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -96,10 +96,13 @@ static void ima_rdwr_violation_check(struct file *file, if (!iint) iint = integrity_iint_find(inode); /* IMA_MEASURE is set from reader side */ - if (iint && (iint->flags & IMA_MEASURE)) + if (iint && test_bit(IMA_MUST_MEASURE, + &iint->atomic_flags)) send_tomtou = true; } } else { + if (must_measure) + set_bit(IMA_MUST_MEASURE, &iint->atomic_flags); if ((atomic_read(&inode->i_writecount) > 0) && must_measure) send_writers = true; } @@ -121,21 +124,24 @@ static void ima_check_last_writer(struct integrity_iint_cache *iint, struct inode *inode, struct file *file) { fmode_t mode = file->f_mode; + bool update; if (!(mode & FMODE_WRITE)) return; - inode_lock(inode); + mutex_lock(&iint->mutex); if (atomic_read(&inode->i_writecount) == 1) { + update = test_and_clear_bit(IMA_UPDATE_XATTR, + &iint->atomic_flags); if ((iint->version != inode->i_version) || (iint->flags & IMA_NEW_FILE)) { iint->flags &= ~(IMA_DONE_MASK | IMA_NEW_FILE); iint->measured_pcrs = 0; - if (iint->flags & IMA_APPRAISE) + if (update) ima_update_xattr(iint, file); } } - inode_unlock(inode); + mutex_unlock(&iint->mutex); } /** @@ -168,7 +174,7 @@ static int process_measurement(struct file *file, char *buf, loff_t size, char *pathbuf = NULL; char filename[NAME_MAX]; const char *pathname = NULL; - int rc = -ENOMEM, action, must_appraise; + int rc = 0, action, must_appraise = 0; int pcr = CONFIG_IMA_MEASURE_PCR_IDX; struct evm_ima_xattr_data *xattr_value = NULL; int xattr_len = 0; @@ -199,17 +205,31 @@ static int process_measurement(struct file *file, char *buf, loff_t size, if (action) { iint = integrity_inode_get(inode); if (!iint) - goto out; + rc = -ENOMEM; } - if (violation_check) { + if (!rc && violation_check) ima_rdwr_violation_check(file, iint, action & IMA_MEASURE, &pathbuf, &pathname); - if (!action) { - rc = 0; - goto out_free; - } - } + + inode_unlock(inode); + + if (rc) + goto out; + if (!action) + goto out; + + mutex_lock(&iint->mutex); + + if (test_and_clear_bit(IMA_CHANGE_ATTR, &iint->atomic_flags)) + /* reset appraisal flags if ima_inode_post_setattr was called */ + iint->flags &= ~(IMA_APPRAISE | IMA_APPRAISED | + IMA_APPRAISE_SUBMASK | IMA_APPRAISED_SUBMASK | + IMA_ACTION_FLAGS); + + if (test_and_clear_bit(IMA_CHANGE_XATTR, &iint->atomic_flags)) + /* reset all flags if ima_inode_setxattr was called */ + iint->flags &= ~IMA_DONE_MASK; /* Determine if already appraised/measured based on bitmask * (IMA_MEASURE, IMA_MEASURED, IMA_XXXX_APPRAISE, IMA_XXXX_APPRAISED, @@ -227,7 +247,7 @@ static int process_measurement(struct file *file, char *buf, loff_t size, if (!action) { if (must_appraise) rc = ima_get_cache_status(iint, func); - goto out_digsig; + goto out_locked; } template_desc = ima_template_desc_current(); @@ -240,7 +260,7 @@ static int process_measurement(struct file *file, char *buf, loff_t size, rc = ima_collect_measurement(iint, file, buf, size, hash_algo); if (rc != 0 && rc != -EBADF && rc != -EINVAL) - goto out_digsig; + goto out_locked; if (!pathbuf) /* ima_rdwr_violation possibly pre-fetched */ pathname = ima_d_path(&file->f_path, &pathbuf, filename); @@ -248,26 +268,32 @@ static int process_measurement(struct file *file, char *buf, loff_t size, if (action & IMA_MEASURE) ima_store_measurement(iint, file, pathname, xattr_value, xattr_len, pcr); - if (rc == 0 && (action & IMA_APPRAISE_SUBMASK)) + if (rc == 0 && (action & IMA_APPRAISE_SUBMASK)) { + inode_lock(inode); rc = ima_appraise_measurement(func, iint, file, pathname, xattr_value, xattr_len, opened); + inode_unlock(inode); + } if (action & IMA_AUDIT) ima_audit_measurement(iint, pathname); if ((file->f_flags & O_DIRECT) && (iint->flags & IMA_PERMIT_DIRECTIO)) rc = 0; -out_digsig: - if ((mask & MAY_WRITE) && (iint->flags & IMA_DIGSIG) && +out_locked: + if ((mask & MAY_WRITE) && test_bit(IMA_DIGSIG, &iint->atomic_flags) && !(iint->flags & IMA_NEW_FILE)) rc = -EACCES; + mutex_unlock(&iint->mutex); kfree(xattr_value); -out_free: +out: if (pathbuf) __putname(pathbuf); -out: - inode_unlock(inode); - if ((rc && must_appraise) && (ima_appraise & IMA_APPRAISE_ENFORCE)) - return -EACCES; + if (must_appraise) { + if (rc && (ima_appraise & IMA_APPRAISE_ENFORCE)) + return -EACCES; + if (file->f_mode & FMODE_WRITE) + set_bit(IMA_UPDATE_XATTR, &iint->atomic_flags); + } return 0; } diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index e324bf98c856..c64ea8f88f66 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -29,11 +29,10 @@ /* iint cache flags */ #define IMA_ACTION_FLAGS 0xff000000 #define IMA_ACTION_RULE_FLAGS 0x06000000 -#define IMA_DIGSIG 0x01000000 -#define IMA_DIGSIG_REQUIRED 0x02000000 -#define IMA_PERMIT_DIRECTIO 0x04000000 -#define IMA_NEW_FILE 0x08000000 -#define EVM_IMMUTABLE_DIGSIG 0x10000000 +#define IMA_DIGSIG_REQUIRED 0x01000000 +#define IMA_PERMIT_DIRECTIO 0x02000000 +#define IMA_NEW_FILE 0x04000000 +#define EVM_IMMUTABLE_DIGSIG 0x08000000 #define IMA_DO_MASK (IMA_MEASURE | IMA_APPRAISE | IMA_AUDIT | \ IMA_APPRAISE_SUBMASK) @@ -54,6 +53,13 @@ #define IMA_APPRAISED_SUBMASK (IMA_FILE_APPRAISED | IMA_MMAP_APPRAISED | \ IMA_BPRM_APPRAISED | IMA_READ_APPRAISED) +/* iint cache atomic_flags */ +#define IMA_CHANGE_XATTR 0 +#define IMA_UPDATE_XATTR 1 +#define IMA_CHANGE_ATTR 2 +#define IMA_DIGSIG 3 +#define IMA_MUST_MEASURE 4 + enum evm_ima_xattr_type { IMA_XATTR_DIGEST = 0x01, EVM_XATTR_HMAC, @@ -102,10 +108,12 @@ struct signature_v2_hdr { /* integrity data associated with an inode */ struct integrity_iint_cache { struct rb_node rb_node; /* rooted in integrity_iint_tree */ + struct mutex mutex; /* protects: version, flags, digest */ struct inode *inode; /* back pointer to inode in question */ u64 version; /* track inode changes */ unsigned long flags; unsigned long measured_pcrs; + unsigned long atomic_flags; enum integrity_status ima_file_status:4; enum integrity_status ima_mmap_status:4; enum integrity_status ima_bprm_status:4; From da1b0029f527a9b4204e90ba6f14ee139fd76f9e Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Thu, 29 Sep 2016 10:04:52 -0400 Subject: [PATCH 05/11] ima: support new "hash" and "dont_hash" policy actions The builtin ima_appraise_tcb policy, which is specified on the boot command line, can be replaced with a custom policy, normally early in the boot process. Custom policies can be more restrictive in some ways, like requiring file signatures, but can be less restrictive in other ways, like not appraising mutable files. With a less restrictive policy in place, files in the builtin policy might not be hashed and labeled with a security.ima hash. On reboot, files which should be labeled in the ima_appraise_tcb are not labeled, possibly preventing the system from booting properly. To resolve this problem, this patch extends the existing IMA policy actions "measure", "dont_measure", "appraise", "dont_appraise", and "audit" with "hash" and "dont_hash". The new "hash" action will write the file hash as security.ima, but without requiring the file to be appraised as well. For example, the builtin ima_appraise_tcb policy includes the rule, "appraise fowner=0". Adding the "hash fowner=0" rule to a custom policy, will cause the needed file hashes to be calculated and written as security.ima xattrs. Signed-off-by: Mimi Zohar Signed-off-by: Stefan Berger --- Documentation/ABI/testing/ima_policy | 3 ++- security/integrity/ima/ima_api.c | 2 +- security/integrity/ima/ima_appraise.c | 16 +++++++------- security/integrity/ima/ima_main.c | 12 +++++++++++ security/integrity/ima/ima_policy.c | 30 +++++++++++++++++++++++++-- security/integrity/integrity.h | 23 +++++++++++--------- 6 files changed, 65 insertions(+), 21 deletions(-) diff --git a/Documentation/ABI/testing/ima_policy b/Documentation/ABI/testing/ima_policy index e76432b9954d..2028f2d093b2 100644 --- a/Documentation/ABI/testing/ima_policy +++ b/Documentation/ABI/testing/ima_policy @@ -17,7 +17,8 @@ Description: rule format: action [condition ...] - action: measure | dont_measure | appraise | dont_appraise | audit + action: measure | dont_measure | appraise | dont_appraise | + audit | hash | dont_hash condition:= base | lsm [option] base: [[func=] [mask=] [fsmagic=] [fsuuid=] [uid=] [euid=] [fowner=]] diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c index c7e8db0ea4c0..877f446fdca2 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -174,7 +174,7 @@ err_out: */ int ima_get_action(struct inode *inode, int mask, enum ima_hooks func, int *pcr) { - int flags = IMA_MEASURE | IMA_AUDIT | IMA_APPRAISE; + int flags = IMA_MEASURE | IMA_AUDIT | IMA_APPRAISE | IMA_HASH; flags &= ima_policy_flag; diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index ea1245606a14..f2803a40ff82 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -46,14 +46,15 @@ bool is_ima_appraise_enabled(void) /* * ima_must_appraise - set appraise flag * - * Return 1 to appraise + * Return 1 to appraise or hash */ int ima_must_appraise(struct inode *inode, int mask, enum ima_hooks func) { if (!ima_appraise) return 0; - return ima_match_policy(inode, func, mask, IMA_APPRAISE, NULL); + return ima_match_policy(inode, func, mask, IMA_APPRAISE | IMA_HASH, + NULL); } static int ima_fix_xattr(struct dentry *dentry, @@ -324,7 +325,8 @@ void ima_update_xattr(struct integrity_iint_cache *iint, struct file *file) if (test_bit(IMA_DIGSIG, &iint->atomic_flags)) return; - if (iint->ima_file_status != INTEGRITY_PASS) + if ((iint->ima_file_status != INTEGRITY_PASS) && + !(iint->flags & IMA_HASH)) return; rc = ima_collect_measurement(iint, file, NULL, 0, ima_hash_algo); @@ -349,19 +351,19 @@ void ima_inode_post_setattr(struct dentry *dentry) { struct inode *inode = d_backing_inode(dentry); struct integrity_iint_cache *iint; - int must_appraise; + int action; if (!(ima_policy_flag & IMA_APPRAISE) || !S_ISREG(inode->i_mode) || !(inode->i_opflags & IOP_XATTR)) return; - must_appraise = ima_must_appraise(inode, MAY_ACCESS, POST_SETATTR); - if (!must_appraise) + action = ima_must_appraise(inode, MAY_ACCESS, POST_SETATTR); + if (!action) __vfs_removexattr(dentry, XATTR_NAME_IMA); iint = integrity_iint_find(inode); if (iint) { set_bit(IMA_CHANGE_ATTR, &iint->atomic_flags); - if (!must_appraise) + if (!action) clear_bit(IMA_UPDATE_XATTR, &iint->atomic_flags); } } diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index edf4e0717494..be1987e13c43 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -243,6 +243,18 @@ static int process_measurement(struct file *file, char *buf, loff_t size, if ((action & IMA_MEASURE) && (iint->measured_pcrs & (0x1 << pcr))) action ^= IMA_MEASURE; + /* HASH sets the digital signature and update flags, nothing else */ + if ((action & IMA_HASH) && + !(test_bit(IMA_DIGSIG, &iint->atomic_flags))) { + xattr_len = ima_read_xattr(file_dentry(file), &xattr_value); + if ((xattr_value && xattr_len > 2) && + (xattr_value->type == EVM_IMA_XATTR_DIGSIG)) + set_bit(IMA_DIGSIG, &iint->atomic_flags); + iint->flags |= IMA_HASHED; + action ^= IMA_HASH; + set_bit(IMA_UPDATE_XATTR, &iint->atomic_flags); + } + /* Nothing to do, just return existing appraised status */ if (!action) { if (must_appraise) diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index ee4613fa5840..93dcf1bf92a8 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -40,6 +40,8 @@ #define APPRAISE 0x0004 /* same as IMA_APPRAISE */ #define DONT_APPRAISE 0x0008 #define AUDIT 0x0040 +#define HASH 0x0100 +#define DONT_HASH 0x0200 #define INVALID_PCR(a) (((a) < 0) || \ (a) >= (FIELD_SIZEOF(struct integrity_iint_cache, measured_pcrs) * 8)) @@ -380,8 +382,10 @@ int ima_match_policy(struct inode *inode, enum ima_hooks func, int mask, action |= entry->flags & IMA_ACTION_FLAGS; action |= entry->action & IMA_DO_MASK; - if (entry->action & IMA_APPRAISE) + if (entry->action & IMA_APPRAISE) { action |= get_subaction(entry, func); + action ^= IMA_HASH; + } if (entry->action & IMA_DO_MASK) actmask &= ~(entry->action | entry->action << 1); @@ -521,7 +525,7 @@ enum { Opt_err = -1, Opt_measure = 1, Opt_dont_measure, Opt_appraise, Opt_dont_appraise, - Opt_audit, + Opt_audit, Opt_hash, Opt_dont_hash, Opt_obj_user, Opt_obj_role, Opt_obj_type, Opt_subj_user, Opt_subj_role, Opt_subj_type, Opt_func, Opt_mask, Opt_fsmagic, @@ -538,6 +542,8 @@ static match_table_t policy_tokens = { {Opt_appraise, "appraise"}, {Opt_dont_appraise, "dont_appraise"}, {Opt_audit, "audit"}, + {Opt_hash, "hash"}, + {Opt_dont_hash, "dont_hash"}, {Opt_obj_user, "obj_user=%s"}, {Opt_obj_role, "obj_role=%s"}, {Opt_obj_type, "obj_type=%s"}, @@ -671,6 +677,22 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry) entry->action = AUDIT; break; + case Opt_hash: + ima_log_string(ab, "action", "hash"); + + if (entry->action != UNKNOWN) + result = -EINVAL; + + entry->action = HASH; + break; + case Opt_dont_hash: + ima_log_string(ab, "action", "dont_hash"); + + if (entry->action != UNKNOWN) + result = -EINVAL; + + entry->action = DONT_HASH; + break; case Opt_func: ima_log_string(ab, "func", args[0].from); @@ -1040,6 +1062,10 @@ int ima_policy_show(struct seq_file *m, void *v) seq_puts(m, pt(Opt_dont_appraise)); if (entry->action & AUDIT) seq_puts(m, pt(Opt_audit)); + if (entry->action & HASH) + seq_puts(m, pt(Opt_hash)); + if (entry->action & DONT_HASH) + seq_puts(m, pt(Opt_dont_hash)); seq_puts(m, " "); diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index c64ea8f88f66..50a8e3365df7 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -25,6 +25,8 @@ #define IMA_COLLECTED 0x00000020 #define IMA_AUDIT 0x00000040 #define IMA_AUDITED 0x00000080 +#define IMA_HASH 0x00000100 +#define IMA_HASHED 0x00000200 /* iint cache flags */ #define IMA_ACTION_FLAGS 0xff000000 @@ -35,19 +37,20 @@ #define EVM_IMMUTABLE_DIGSIG 0x08000000 #define IMA_DO_MASK (IMA_MEASURE | IMA_APPRAISE | IMA_AUDIT | \ - IMA_APPRAISE_SUBMASK) + IMA_HASH | IMA_APPRAISE_SUBMASK) #define IMA_DONE_MASK (IMA_MEASURED | IMA_APPRAISED | IMA_AUDITED | \ - IMA_COLLECTED | IMA_APPRAISED_SUBMASK) + IMA_HASHED | IMA_COLLECTED | \ + IMA_APPRAISED_SUBMASK) /* iint subaction appraise cache flags */ -#define IMA_FILE_APPRAISE 0x00000100 -#define IMA_FILE_APPRAISED 0x00000200 -#define IMA_MMAP_APPRAISE 0x00000400 -#define IMA_MMAP_APPRAISED 0x00000800 -#define IMA_BPRM_APPRAISE 0x00001000 -#define IMA_BPRM_APPRAISED 0x00002000 -#define IMA_READ_APPRAISE 0x00004000 -#define IMA_READ_APPRAISED 0x00008000 +#define IMA_FILE_APPRAISE 0x00001000 +#define IMA_FILE_APPRAISED 0x00002000 +#define IMA_MMAP_APPRAISE 0x00004000 +#define IMA_MMAP_APPRAISED 0x00008000 +#define IMA_BPRM_APPRAISE 0x00010000 +#define IMA_BPRM_APPRAISED 0x00020000 +#define IMA_READ_APPRAISE 0x00040000 +#define IMA_READ_APPRAISED 0x00080000 #define IMA_APPRAISE_SUBMASK (IMA_FILE_APPRAISE | IMA_MMAP_APPRAISE | \ IMA_BPRM_APPRAISE | IMA_READ_APPRAISE) #define IMA_APPRAISED_SUBMASK (IMA_FILE_APPRAISED | IMA_MMAP_APPRAISED | \ From 72bf83b0c978c495ce9f6bfeee1ccd34478b05e6 Mon Sep 17 00:00:00 2001 From: Joe Perches Date: Thu, 16 Nov 2017 07:27:29 -0800 Subject: [PATCH 06/11] ima: Fix line continuation format Line continuations with excess spacing causes unexpected output. Based on commit 6f76b6fcaa60 ("CodingStyle: Document the exception of not splitting user-visible strings, for grepping") recommendation. Signed-off-by: Joe Perches Signed-off-by: Mimi Zohar --- security/integrity/ima/ima_template.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/security/integrity/ima/ima_template.c b/security/integrity/ima/ima_template.c index 7412d0291ab9..30db39b23804 100644 --- a/security/integrity/ima/ima_template.c +++ b/security/integrity/ima/ima_template.c @@ -377,8 +377,7 @@ int ima_restore_measurement_list(loff_t size, void *buf) break; if (hdr[HDR_TEMPLATE_NAME].len >= MAX_TEMPLATE_NAME_LEN) { - pr_err("attempting to restore a template name \ - that is too long\n"); + pr_err("attempting to restore a template name that is too long\n"); ret = -EINVAL; break; } @@ -389,8 +388,8 @@ int ima_restore_measurement_list(loff_t size, void *buf) template_name[hdr[HDR_TEMPLATE_NAME].len] = 0; if (strcmp(template_name, "ima") == 0) { - pr_err("attempting to restore an unsupported \ - template \"%s\" failed\n", template_name); + pr_err("attempting to restore an unsupported template \"%s\" failed\n", + template_name); ret = -EINVAL; break; } @@ -410,8 +409,8 @@ int ima_restore_measurement_list(loff_t size, void *buf) &(template_desc->fields), &(template_desc->num_fields)); if (ret < 0) { - pr_err("attempting to restore the template fmt \"%s\" \ - failed\n", template_desc->fmt); + pr_err("attempting to restore the template fmt \"%s\" failed\n", + template_desc->fmt); ret = -EINVAL; break; } From 4e8581eefe720f8d990b892a8c9d298875e1a299 Mon Sep 17 00:00:00 2001 From: Roberto Sassu Date: Thu, 30 Nov 2017 11:56:02 +0100 Subject: [PATCH 07/11] ima: pass filename to ima_rdwr_violation_check() ima_rdwr_violation_check() retrieves the full path of a measured file by calling ima_d_path(). If process_measurement() calls this function, it reuses the pointer and passes it to the functions to measure/appraise/audit an accessed file. After commit bc15ed663e7e ("ima: fix ima_d_path() possible race with rename"), ima_d_path() first tries to retrieve the full path by calling d_absolute_path() and, if there is an error, copies the dentry name to the buffer passed as argument. However, ima_rdwr_violation_check() passes to ima_d_path() the pointer of a local variable. process_measurement() might be reusing the pointer to an area in the stack which may have been already overwritten after ima_rdwr_violation_check() returned. Correct this issue by passing to ima_rdwr_violation_check() the pointer of a buffer declared in process_measurement(). Fixes: bc15ed663e7e ("ima: fix ima_d_path() possible race with rename") Signed-off-by: Roberto Sassu Signed-off-by: Mimi Zohar --- security/integrity/ima/ima_main.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index be1987e13c43..0abc7d0db90b 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -84,10 +84,10 @@ static void ima_rdwr_violation_check(struct file *file, struct integrity_iint_cache *iint, int must_measure, char **pathbuf, - const char **pathname) + const char **pathname, + char *filename) { struct inode *inode = file_inode(file); - char filename[NAME_MAX]; fmode_t mode = file->f_mode; bool send_tomtou = false, send_writers = false; @@ -210,7 +210,7 @@ static int process_measurement(struct file *file, char *buf, loff_t size, if (!rc && violation_check) ima_rdwr_violation_check(file, iint, action & IMA_MEASURE, - &pathbuf, &pathname); + &pathbuf, &pathname, filename); inode_unlock(inode); From 9c655be0644429b71396347887b43676ab4f6781 Mon Sep 17 00:00:00 2001 From: "Bruno E. O. Meneguele" Date: Tue, 5 Dec 2017 11:35:32 -0200 Subject: [PATCH 08/11] ima: log message to module appraisal error Simple but useful message log to the user in case of module appraise is forced and fails due to the lack of file descriptor, that might be caused by kmod calls to compressed modules. Signed-off-by: Bruno E. O. Meneguele Signed-off-by: Mimi Zohar --- security/integrity/ima/ima_main.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 0abc7d0db90b..21330d0455b0 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -404,8 +404,10 @@ int ima_read_file(struct file *file, enum kernel_read_file_id read_id) if (!file && read_id == READING_MODULE) { if (!sig_enforce && (ima_appraise & IMA_APPRAISE_MODULES) && - (ima_appraise & IMA_APPRAISE_ENFORCE)) + (ima_appraise & IMA_APPRAISE_ENFORCE)) { + pr_err("impossible to appraise a module without a file descriptor. sig_enforce kernel parameter might help\n"); return -EACCES; /* INTEGRITY_UNKNOWN */ + } return 0; /* We rely on module signature checking */ } return 0; From 02c324a55ed9ee4d790eaa9ac8e7cdecbe3e0a22 Mon Sep 17 00:00:00 2001 From: Jeff Layton Date: Thu, 6 Jul 2017 10:54:21 -0400 Subject: [PATCH 09/11] integrity: remove unneeded initializations in integrity_iint_cache entries The init_once routine memsets the whole object to 0, and then explicitly sets some of the fields to 0 again. Just remove the explicit initializations. Signed-off-by: Jeff Layton Signed-off-by: Mimi Zohar --- security/integrity/iint.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/security/integrity/iint.c b/security/integrity/iint.c index d726ba23a178..fc38ca08dbb5 100644 --- a/security/integrity/iint.c +++ b/security/integrity/iint.c @@ -153,15 +153,11 @@ static void init_once(void *foo) struct integrity_iint_cache *iint = foo; memset(iint, 0, sizeof(*iint)); - iint->version = 0; - iint->flags = 0UL; - iint->atomic_flags = 0; iint->ima_file_status = INTEGRITY_UNKNOWN; iint->ima_mmap_status = INTEGRITY_UNKNOWN; iint->ima_bprm_status = INTEGRITY_UNKNOWN; iint->ima_read_status = INTEGRITY_UNKNOWN; iint->evm_status = INTEGRITY_UNKNOWN; - iint->measured_pcrs = 0; mutex_init(&iint->mutex); } From a2a2c3c8580a9158bca61221648fd6d5c98c443a Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Wed, 27 Sep 2017 08:39:54 +0200 Subject: [PATCH 10/11] ima: Use i_version only when filesystem supports it i_version is only supported by a filesystem when the SB_I_VERSION flag is set. This patch tests for the SB_I_VERSION flag before using i_version. If we can't use i_version to detect a file change then we must assume the file has changed in the last_writer path and remeasure it. On filesystems without i_version support IMA used to measure a file only once and didn't detect any changes to a file. With this patch IMA now works properly on these filesystems. Signed-off-by: Sascha Hauer Reviewed-by: Jeff Layton Signed-off-by: Mimi Zohar --- security/integrity/ima/ima_main.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 21330d0455b0..6d78cb26784d 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -133,7 +133,8 @@ static void ima_check_last_writer(struct integrity_iint_cache *iint, if (atomic_read(&inode->i_writecount) == 1) { update = test_and_clear_bit(IMA_UPDATE_XATTR, &iint->atomic_flags); - if ((iint->version != inode->i_version) || + if (!IS_I_VERSION(inode) || + (iint->version != inode->i_version) || (iint->flags & IMA_NEW_FILE)) { iint->flags &= ~(IMA_DONE_MASK | IMA_NEW_FILE); iint->measured_pcrs = 0; From 36447456e1cca853188505f2a964dbbeacfc7a7a Mon Sep 17 00:00:00 2001 From: Mike Rapoport Date: Wed, 17 Jan 2018 20:27:11 +0200 Subject: [PATCH 11/11] ima/policy: fix parsing of fsuuid The switch to uuid_t invereted the logic of verfication that &entry->fsuuid is zero during parsing of "fsuuid=" rule. Instead of making sure the &entry->fsuuid field is not attempted to be overwritten, we bail out for perfectly correct rule. Fixes: 787d8c530af7 ("ima/policy: switch to use uuid_t") Signed-off-by: Mike Rapoport Cc: stable@vger.kernel.org Signed-off-by: Mimi Zohar --- security/integrity/ima/ima_policy.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index 93dcf1bf92a8..915f5572c6ff 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -765,7 +765,7 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry) case Opt_fsuuid: ima_log_string(ab, "fsuuid", args[0].from); - if (uuid_is_null(&entry->fsuuid)) { + if (!uuid_is_null(&entry->fsuuid)) { result = -EINVAL; break; }