Since fsverity signatures are in PKCS#7 format, handle them as the same as kernel modules, using the modsig code. The main differences with modsig are: ima_read_fsverity_sig() gets the fsverity signature with fsverity_get_signature() instead of getting it from the file content; ima_collect_fsverity() gets the data to be hashed from fsverity_get_formatted_digest(), instead of hashing the file content. Signed-off-by: Roberto Sassu <roberto.sassu@xxxxxxxxxx> --- Documentation/ABI/testing/ima_policy | 10 ++++ Documentation/security/IMA-templates.rst | 7 ++- include/linux/evm.h | 5 ++ security/integrity/ima/ima.h | 19 ++++++ security/integrity/ima/ima_api.c | 38 ++++-------- security/integrity/ima/ima_appraise.c | 67 ++++++++++++++++++--- security/integrity/ima/ima_main.c | 32 ++++++++++ security/integrity/ima/ima_modsig.c | 75 ++++++++++++++++++++++++ 8 files changed, 217 insertions(+), 36 deletions(-) diff --git a/Documentation/ABI/testing/ima_policy b/Documentation/ABI/testing/ima_policy index 444bb7ccbe03..8602f08d06bb 100644 --- a/Documentation/ABI/testing/ima_policy +++ b/Documentation/ABI/testing/ima_policy @@ -151,6 +151,16 @@ Description: appraise func=SETXATTR_CHECK appraise_algos=sha256,sha384,sha512 + Example of measure and appraise rules allowing fs-verity + signed digests on a particular filesystem identified by + it's fsuuid: + + measure func=BPRM_CHECK digest_type=hash|verity \ + fsuuid=... template=ima-modsig + appraise func=BPRM_CHECK digest_type=hash|verity \ + ima_appraise_type=imasig fsuuid=... + + Example of measure rule allowing fs-verity's digests on a particular filesystem with indication of type of digest. diff --git a/Documentation/security/IMA-templates.rst b/Documentation/security/IMA-templates.rst index 5e31513e8ec4..96654e72a36e 100644 --- a/Documentation/security/IMA-templates.rst +++ b/Documentation/security/IMA-templates.rst @@ -68,11 +68,12 @@ descriptors by adding their identifier to the format string - 'd-ng': the digest of the event, calculated with an arbitrary hash algorithm (field format: [<hash algo>:]digest, where the digest prefix is shown only if the hash algorithm is not SHA1 or MD5); - - 'd-modsig': the digest of the event without the appended modsig; + - 'd-modsig': the digest of the event without the appended modsig, or the + digest of an fsverity formatted digest; - 'd-type': the type of file digest (e.g. hash, verity[1]); - 'n-ng': the name of the event, without size limitations; - - 'sig': the file signature, or the EVM portable signature if the file - signature is not found; + - 'sig': the file signature, based on either the file's/fsverity's digest[1], + or the EVM portable signature if the file signature is not found; - 'modsig' the appended file signature; - 'buf': the buffer data that was used to generate the hash without size limitations; - 'evmsig': the EVM portable signature; diff --git a/include/linux/evm.h b/include/linux/evm.h index 4c374be70247..3da25393b011 100644 --- a/include/linux/evm.h +++ b/include/linux/evm.h @@ -14,6 +14,11 @@ struct integrity_iint_cache; +static inline bool evm_protects_fsverity(void) +{ + return false; +} + #ifdef CONFIG_EVM extern int evm_set_key(void *key, size_t keylen); extern enum integrity_status evm_verifyxattr(struct dentry *dentry, diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index 78395bed7fad..4a45a7b5743b 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -380,26 +380,45 @@ static inline int ima_read_xattr(struct dentry *dentry, #endif /* CONFIG_IMA_APPRAISE */ #ifdef CONFIG_IMA_APPRAISE_MODSIG +bool ima_modsig_is_verity(const struct modsig *modsig); int ima_read_modsig(enum ima_hooks func, const void *buf, loff_t buf_len, struct modsig **modsig); +int ima_read_fsverity_sig(struct inode *inode, struct modsig **modsig); void ima_collect_modsig(struct modsig *modsig, const void *buf, loff_t size); +void ima_collect_fsverity(struct modsig *modsig, const void *buf, loff_t size); int ima_get_modsig_digest(const struct modsig *modsig, enum hash_algo *algo, const u8 **digest, u32 *digest_size); int ima_get_raw_modsig(const struct modsig *modsig, const void **data, u32 *data_len); void ima_free_modsig(struct modsig *modsig); #else +static inline bool ima_modsig_is_verity(const struct modsig *modsig) +{ + return false; +} + static inline int ima_read_modsig(enum ima_hooks func, const void *buf, loff_t buf_len, struct modsig **modsig) { return -EOPNOTSUPP; } +static inline int ima_read_fsverity_sig(struct inode *inode, + struct modsig **modsig) +{ + return -EOPNOTSUPP; +} + static inline void ima_collect_modsig(struct modsig *modsig, const void *buf, loff_t size) { } +static inline void ima_collect_fsverity(struct modsig *modsig, const void *buf, + loff_t size) +{ +} + static inline int ima_get_modsig_digest(const struct modsig *modsig, enum hash_algo *algo, const u8 **digest, u32 *digest_size) diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c index 8760c4874f7d..369f2222dd55 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -201,23 +201,6 @@ int ima_get_action(struct user_namespace *mnt_userns, struct inode *inode, allowed_algos); } -static int ima_get_verity_digest(struct integrity_iint_cache *iint, - struct ima_digest_data *hash) -{ - u8 verity_digest[FS_VERITY_MAX_DIGEST_SIZE]; - enum hash_algo verity_alg; - int rc; - - rc = fsverity_get_digest(iint->inode, verity_digest, &verity_alg); - if (rc) - return -EINVAL; - if (hash->algo != verity_alg) - return -EINVAL; - hash->length = hash_digest_size[verity_alg]; - memcpy(hash->digest, verity_digest, hash->length); - return 0; -} - /* * ima_collect_measurement - collect file measurement * @@ -249,8 +232,12 @@ int ima_collect_measurement(struct integrity_iint_cache *iint, * the file digest without collecting the modsig in a previous * measurement rule. */ - if (modsig) - ima_collect_modsig(modsig, buf, size); + if (modsig) { + if (!ima_modsig_is_verity(modsig)) + ima_collect_modsig(modsig, buf, size); + else + ima_collect_fsverity(modsig, buf, size); + } if (iint->flags & IMA_COLLECTED) goto out; @@ -266,14 +253,13 @@ int ima_collect_measurement(struct integrity_iint_cache *iint, /* Initialize hash digest to 0's in case of failure */ memset(&hash.digest, 0, sizeof(hash.digest)); - if (buf) { + if (buf && !(iint->flags & IMA_VERITY_DIGEST)) { result = ima_calc_buffer_hash(buf, size, &hash.hdr); - } else if (iint->flags & IMA_VERITY_ALLOWED) { - result = ima_get_verity_digest(iint, &hash.hdr); - if (result < 0) - result = ima_calc_file_hash(file, &hash.hdr); - else - iint->flags |= IMA_VERITY_DIGEST; + } else if (buf && (iint->flags & IMA_VERITY_DIGEST)) { + hash.hdr.length = hash_digest_size[algo]; + memcpy(hash.hdr.digest, + ((struct fsverity_formatted_digest *)buf)->digest, + hash_digest_size[algo]); } else { result = ima_calc_file_hash(file, &hash.hdr); } diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index 17232bbfb9f9..f8dde59e64f5 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -320,7 +320,7 @@ static int modsig_verify(enum ima_hooks func, const struct modsig *modsig, rc = integrity_modsig_verify(INTEGRITY_KEYRING_IMA, modsig); if (IS_ENABLED(CONFIG_INTEGRITY_PLATFORM_KEYRING) && rc && - func == KEXEC_KERNEL_CHECK) + func == KEXEC_KERNEL_CHECK && !ima_modsig_is_verity(modsig)) rc = integrity_modsig_verify(INTEGRITY_KEYRING_PLATFORM, modsig); if (rc) { @@ -333,6 +333,50 @@ static int modsig_verify(enum ima_hooks func, const struct modsig *modsig, return rc; } +/* + * fsverity_verify - verify fsverity signature + * + * Verify whether the fsverity signature is valid. + * + * Return 0 on success, error code otherwise. + */ +static int fsverity_verify(struct integrity_iint_cache *iint, + const struct modsig *modsig, + enum integrity_status *status, const char **cause) +{ + int rc = -EINVAL; + + if (!modsig || !ima_modsig_is_verity(modsig)) { + if (!evm_protects_fsverity()) { + *cause = "EVM-fsverity-not-protected"; + *status = INTEGRITY_FAIL; + return rc; + } + + if (*status != INTEGRITY_PASS_IMMUTABLE) { + if (iint->flags & IMA_DIGSIG_REQUIRED) { + *cause = "IMA-signature-required"; + *status = INTEGRITY_FAIL; + return rc; + } + clear_bit(IMA_DIGSIG, &iint->atomic_flags); + } else { + set_bit(IMA_DIGSIG, &iint->atomic_flags); + } + + /* + * EVM already verified the actual fsverity digest, nothing else + * is required. + */ + *status = INTEGRITY_PASS; + rc = 0; + } else { + rc = modsig_verify(NONE, modsig, status, cause); + } + + return rc; +} + /* * ima_check_blacklist - determine if the binary is blacklisted. * @@ -352,7 +396,8 @@ int ima_check_blacklist(struct integrity_iint_cache *iint, if (!(iint->flags & IMA_CHECK_BLACKLIST)) return 0; - if (iint->flags & IMA_MODSIG_ALLOWED && modsig) { + if (iint->flags & IMA_MODSIG_ALLOWED && modsig && + !ima_modsig_is_verity(modsig)) { ima_get_modsig_digest(modsig, &hash_algo, &digest, &digestsize); rc = is_binary_blacklisted(digest, digestsize); @@ -385,14 +430,16 @@ int ima_appraise_measurement(enum ima_hooks func, struct inode *inode = d_backing_inode(dentry); enum integrity_status status = INTEGRITY_UNKNOWN; int rc = xattr_len; - bool try_modsig = iint->flags & IMA_MODSIG_ALLOWED && modsig; + bool try_modsig = iint->flags & IMA_MODSIG_ALLOWED && modsig && + !ima_modsig_is_verity(modsig); + bool try_fsverity = iint->flags & IMA_VERITY_DIGEST; - /* If not appraising a modsig, we need an xattr. */ - if (!(inode->i_opflags & IOP_XATTR) && !try_modsig) + /* If not appraising a modsig or an fsverity file, we need an xattr. */ + if (!(inode->i_opflags & IOP_XATTR) && !try_modsig && !try_fsverity) return INTEGRITY_UNKNOWN; /* If reading the xattr failed and there's no modsig, error out. */ - if (rc <= 0 && !try_modsig) { + if (rc <= 0 && !try_modsig && !try_fsverity) { if (rc && rc != -ENODATA) goto out; @@ -446,6 +493,12 @@ int ima_appraise_measurement(enum ima_hooks func, rc == -ENOKEY)) rc = modsig_verify(func, modsig, &status, &cause); + /* + * If we have a fsverity sig, no modsig and no imasig, then try + * verifying the fsverity sig. + */ + if (try_fsverity) + rc = fsverity_verify(iint, modsig, &status, &cause); out: /* * File signatures on some filesystems can not be properly verified. @@ -463,7 +516,7 @@ int ima_appraise_measurement(enum ima_hooks func, } else if (status != INTEGRITY_PASS) { /* Fix mode, but don't replace file signatures. */ if ((ima_appraise & IMA_APPRAISE_FIX) && !try_modsig && - (!xattr_value || + !try_fsverity && (!xattr_value || xattr_value->type != EVM_IMA_XATTR_DIGSIG)) { if (!ima_fix_xattr(dentry, iint)) status = INTEGRITY_PASS; diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 774accb62275..1f78f31c3e89 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -25,6 +25,7 @@ #include <linux/xattr.h> #include <linux/ima.h> #include <linux/iversion.h> +#include <linux/fsverity.h> #include <linux/fs.h> #include "ima.h" @@ -216,6 +217,8 @@ static int process_measurement(struct file *file, const struct cred *cred, bool violation_check; enum hash_algo hash_algo; unsigned int allowed_algos = 0; + u8 fsverity_buf[FS_VERITY_MAX_FMT_DIGEST_SIZE]; + ssize_t fsverity_buf_len; if (!ima_policy_flag || !S_ISREG(inode->i_mode)) return 0; @@ -330,9 +333,38 @@ static int process_measurement(struct file *file, const struct cred *cred, iint->flags & IMA_MEASURED) action |= IMA_MEASURE; } + + /* + * Read the fsverity sig if allowed by the policy, and allow + * an additional measurement list entry, if needed, based on the + * template format and whether the file was already measured. + */ + if (!modsig && IS_VERITY(inode) && + (iint->flags & IMA_VERITY_ALLOWED)) { + rc = ima_read_fsverity_sig(inode, &modsig); + + if (!rc && ima_template_has_modsig(template_desc) && + iint->flags & IMA_MEASURED) + action |= IMA_MEASURE; + } } hash_algo = ima_get_hash_algo(xattr_value, xattr_len); + /* + * Fsverity verification method is enabled only if all others are not + * available. + */ + if (IS_VERITY(inode) && (iint->flags & IMA_VERITY_ALLOWED) && + !xattr_value && (!modsig || ima_modsig_is_verity(modsig))) { + fsverity_buf_len = fsverity_get_formatted_digest(inode, + fsverity_buf, + &hash_algo); + if (fsverity_buf_len > 0) { + buf = fsverity_buf; + size = fsverity_buf_len; + iint->flags |= IMA_VERITY_DIGEST; + } + } rc = ima_collect_measurement(iint, file, buf, size, hash_algo, modsig); if (rc != 0 && rc != -EBADF && rc != -EINVAL) diff --git a/security/integrity/ima/ima_modsig.c b/security/integrity/ima/ima_modsig.c index fb25723c65bc..66c19846477c 100644 --- a/security/integrity/ima/ima_modsig.c +++ b/security/integrity/ima/ima_modsig.c @@ -9,6 +9,7 @@ */ #include <linux/types.h> +#include <linux/fsverity.h> #include <linux/module_signature.h> #include <keys/asymmetric-type.h> #include <crypto/pkcs7.h> @@ -16,6 +17,8 @@ #include "ima.h" struct modsig { + bool is_verity; + struct pkcs7_message *pkcs7_msg; enum hash_algo hash_algo; @@ -32,6 +35,11 @@ struct modsig { u8 raw_pkcs7[]; }; +bool ima_modsig_is_verity(const struct modsig *modsig) +{ + return modsig->is_verity; +} + /* * ima_read_modsig - Read modsig from buf. * @@ -87,6 +95,51 @@ int ima_read_modsig(enum ima_hooks func, const void *buf, loff_t buf_len, return 0; } +/* + * ima_read_fsverity_sig - Read fsverity sig from inode. + * + * Return: 0 on success, error code otherwise. + */ +int ima_read_fsverity_sig(struct inode *inode, struct modsig **modsig) +{ + struct modsig *hdr; + u8 *signature; + ssize_t signature_size; + int rc; + + signature_size = fsverity_get_signature(inode, &signature); + if (signature_size < 0) + return signature_size; + + /* + * Allocate signature_size additional bytes to hold the raw PKCS#7 data. + */ + hdr = kzalloc(sizeof(*hdr) + signature_size, GFP_KERNEL); + if (!hdr) { + rc = -ENOMEM; + goto out; + } + + hdr->pkcs7_msg = pkcs7_parse_message(signature, signature_size); + if (IS_ERR(hdr->pkcs7_msg)) { + rc = PTR_ERR(hdr->pkcs7_msg); + kfree(hdr); + goto out; + } + + memcpy(hdr->raw_pkcs7, signature, signature_size); + hdr->raw_pkcs7_len = signature_size; + + /* We don't know the hash algorithm yet. */ + hdr->hash_algo = HASH_ALGO__LAST; + hdr->is_verity = true; + + *modsig = hdr; +out: + kfree(signature); + return rc; +} + /** * ima_collect_modsig - Calculate the file hash without the appended signature. * @@ -113,6 +166,28 @@ void ima_collect_modsig(struct modsig *modsig, const void *buf, loff_t size) &modsig->digest_size, &modsig->hash_algo); } +/** + * ima_collect_fsverity - Calculate the digest of the fsverity formatted digest. + * + * Pass the same data used to verify the fsverity signature in + * fs/verity/signature.c. + */ +void ima_collect_fsverity(struct modsig *modsig, const void *buf, loff_t size) +{ + int rc; + + rc = pkcs7_supply_detached_data(modsig->pkcs7_msg, buf, size); + if (rc) + return; + + /* + * Ask the PKCS7 code to calculate the digest of the fsverity formatted + * digest. + */ + rc = pkcs7_get_digest(modsig->pkcs7_msg, &modsig->digest, + &modsig->digest_size, &modsig->hash_algo); +} + int ima_modsig_verify(struct key *keyring, const struct modsig *modsig) { return verify_pkcs7_message_sig(NULL, 0, modsig->pkcs7_msg, keyring, -- 2.32.0