@@ -54,6 +54,9 @@
#define XATTR_IMA_SUFFIX "ima"
#define XATTR_NAME_IMA XATTR_SECURITY_PREFIX XATTR_IMA_SUFFIX
+#define XATTR_DIGEST_LIST_SUFFIX "digest_list"
+#define XATTR_NAME_DIGEST_LIST XATTR_SECURITY_PREFIX XATTR_DIGEST_LIST_SUFFIX
+
#define XATTR_SELINUX_SUFFIX "selinux"
#define XATTR_NAME_SELINUX XATTR_SECURITY_PREFIX XATTR_SELINUX_SUFFIX
@@ -130,6 +130,18 @@ config INTEGRITY_AUDIT
be enabled by specifying 'integrity_audit=1' on the kernel
command line.
+config INTEGRITY_DIGEST_CACHE
+ bool "Enable the integrity digest cache"
+ depends on INTEGRITY
+ default n
+ help
+ This option enables a cache of digests from a digest list, possibly
+ authenticated with a signature.
+
+ The digest cache can be used to make a TPM PCR predictable
+ (by skipping the measurement of cached digests), or for appraisal
+ with already available sources (e.g. RPM packages).
+
source "security/integrity/ima/Kconfig"
source "security/integrity/evm/Kconfig"
@@ -11,6 +11,7 @@ integrity-$(CONFIG_INTEGRITY_SIGNATURE) += digsig.o
integrity-$(CONFIG_INTEGRITY_ASYMMETRIC_KEYS) += digsig_asymmetric.o
integrity-$(CONFIG_INTEGRITY_PLATFORM_KEYRING) += platform_certs/platform_keyring.o
integrity-$(CONFIG_INTEGRITY_MACHINE_KEYRING) += platform_certs/machine_keyring.o
+integrity-$(CONFIG_INTEGRITY_DIGEST_CACHE) += digest_cache.o
integrity-$(CONFIG_LOAD_UEFI_KEYS) += platform_certs/efi_parser.o \
platform_certs/load_uefi.o \
platform_certs/keyring_handler.o
new file mode 100644
@@ -0,0 +1,317 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 IBM Corporation
+ * Copyright (C) 2023 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ *
+ * Implement the integrity digest cache.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/init_task.h>
+#include <linux/fs.h>
+#include <linux/namei.h>
+#include <linux/xattr.h>
+#include <linux/kernel_read_file.h>
+#include <linux/module_signature.h>
+
+#include "integrity.h"
+
+#ifdef pr_fmt
+#undef pr_fmt
+#endif
+#define pr_fmt(fmt) "DIGEST CACHE: "fmt
+
+/**
+ * digest_cache_alloc - Allocate and initialize a new digest cache
+ * @path_str: Path of the digest list
+ * @digest_cache_mask: Actions done by IMA on the digest list
+ *
+ * This function allocates a new digest cache and initializes all fields of
+ * the digest_cache structure.
+ *
+ * Return: A digest_cache structure on success, NULL on error.
+ */
+static struct digest_cache *digest_cache_alloc(char *path_str,
+ u64 digest_cache_mask)
+{
+ struct digest_cache *digest_cache;
+
+ digest_cache = kmalloc(sizeof(*digest_cache), GFP_KERNEL);
+ if (!digest_cache)
+ return digest_cache;
+
+ digest_cache->algo = HASH_ALGO__LAST;
+ digest_cache->path_str = path_str;
+ digest_cache->mask = digest_cache_mask;
+ digest_cache->slots = NULL;
+ digest_cache->num_slots = 0;
+ /*
+ * One for dig_owner of the digest list iint, one for dig_user of the
+ * iint of the inode for which the digest cache is used.
+ */
+ atomic_set(&digest_cache->ref_count, 2);
+ return digest_cache;
+}
+
+/**
+ * digest_cache_free - Free all memory occupied by a digest cache
+ * @digest_cache: Digest cache
+ *
+ * This function frees the digests associated to the digest cache and the
+ * digest cache itself.
+ */
+void digest_cache_free(struct digest_cache *digest_cache)
+{
+ struct digest_cache_entry *p;
+ struct hlist_node *q;
+ int digest_len, i;
+
+ if (!digest_cache)
+ return;
+
+ pr_debug("Free cache (ref count: %d), algo: %s, digest list: %s",
+ atomic_read(&digest_cache->ref_count),
+ hash_algo_name[digest_cache->algo], digest_cache->path_str);
+
+ if (!atomic_dec_and_test(&digest_cache->ref_count))
+ return;
+
+ digest_len = hash_digest_size[digest_cache->algo];
+
+ for (i = 0; i < digest_cache->num_slots; i++) {
+ hlist_for_each_entry_safe(p, q, &digest_cache->slots[i],
+ hnext) {
+ hlist_del(&p->hnext);
+ pr_debug("Remove digest %s:%*phN from digest list %s\n",
+ hash_algo_name[digest_cache->algo],
+ digest_len, p->digest, digest_cache->path_str);
+ kfree(p);
+ }
+ }
+
+ pr_debug("Freed cache (ref count: %d), algo: %s, digest list: %s",
+ atomic_read(&digest_cache->ref_count),
+ hash_algo_name[digest_cache->algo], digest_cache->path_str);
+
+ kfree(digest_cache->path_str);
+ kfree(digest_cache->slots);
+ kfree(digest_cache);
+}
+
+/**
+ * digest_cache_parse_digest_list - Parse a digest list
+ * @digest_cache: Digest cache
+ * @digest_list_path: Path of the digest list
+ * @data: Data to parse
+ * @data_len: Length of @data
+ *
+ * This function parses a digest list. First, it strips the module-style
+ * appended signature, if present. Then, it selects the parser to call from
+ * the beginning of the file name, which is expected to be in the format:
+ * <digest list format>-<digest list file name>.
+ *
+ * Return: Zero on success, a negative value on error.
+ */
+static int digest_cache_parse_digest_list(struct digest_cache *digest_cache,
+ struct path *digest_list_path,
+ void *data, size_t data_len)
+{
+ const size_t marker_len = strlen(MODULE_SIG_STRING);
+ const struct module_signature *sig;
+ size_t sig_len;
+ const void *p;
+ int ret = -EINVAL;
+
+ /* From ima_modsig.c */
+ if (data_len <= marker_len + sizeof(*sig))
+ goto parse;
+
+ p = data + data_len - marker_len;
+ if (memcmp(p, MODULE_SIG_STRING, marker_len))
+ goto parse;
+
+ data_len -= marker_len;
+ sig = (const struct module_signature *)(p - sizeof(*sig));
+
+ sig_len = be32_to_cpu(sig->sig_len);
+ data_len -= sig_len + sizeof(*sig);
+parse:
+ pr_debug("Parsing %s, size: %ld\n", digest_cache->path_str, data_len);
+
+ return ret;
+}
+
+/**
+ * digest_cache_new - Create a new digest cache
+ * @dentry: Dentry of the file being measured/appraised
+ *
+ * This function retrieves the path of the digest list from the
+ * security.digest_list xattr of the file being measured/appraised. It then
+ * opens and parses the digest list, and finally instantiates a new digest
+ * cache.
+ *
+ * After read, the IMA actions done on the digest list are recorded in the
+ * digest cache. The use of the digest cache is allowed for measuring/appraising
+ * a file, only if the same action has been done on the digest list itself.
+ *
+ * The invoked parser will in turn set the digest algorithm, initialize the
+ * hash table and add the extracted digests to the digest cache.
+ *
+ * Return: A new digest cache on success, NULL on error.
+ */
+static struct digest_cache *digest_cache_new(struct dentry *dentry)
+{
+ struct integrity_iint_cache *digest_list_iint;
+ struct digest_cache *digest_cache = NULL;
+ struct path digest_list_path;
+ char *path_str = NULL;
+ struct file *file;
+ void *data = NULL;
+ size_t data_len = 0;
+ struct inode *inode;
+ u64 digest_cache_mask = 0;
+ int ret;
+
+ ret = vfs_getxattr_alloc(&nop_mnt_idmap, dentry, XATTR_NAME_DIGEST_LIST,
+ &path_str, 0, GFP_NOFS);
+ if (ret <= 0) {
+ pr_debug("%s xattr not found in %s\n", XATTR_NAME_DIGEST_LIST,
+ dentry->d_name.name);
+ return digest_cache;
+ }
+
+ pr_debug("Found %s xattr in %s, digest list: %s\n",
+ XATTR_NAME_DIGEST_LIST, dentry->d_name.name, path_str);
+
+ ret = kern_path(path_str, 0, &digest_list_path);
+ if (ret < 0) {
+ pr_debug("Cannot open digest list %s\n", path_str);
+ goto out;
+ }
+
+ inode = d_backing_inode(digest_list_path.dentry);
+
+ digest_list_iint = integrity_inode_get(inode);
+ if (!digest_list_iint) {
+ pr_debug("Cannot get integrity metadata for digest list %s\n",
+ path_str);
+ goto out_path;
+ }
+
+ if (digest_list_iint->dig_owner) {
+ pr_debug("Cache for digest list %s exists\n", path_str);
+ digest_cache = digest_list_iint->dig_owner;
+ atomic_inc(&digest_cache->ref_count);
+ goto out_path;
+ }
+
+ mutex_lock(&digest_list_iint->dig_owner_mutex);
+
+ if (digest_list_iint->dig_owner) {
+ pr_debug("Cache for digest list %s exists\n", path_str);
+ digest_cache = digest_list_iint->dig_owner;
+ atomic_inc(&digest_cache->ref_count);
+ goto out_unlock;
+ }
+
+ file = dentry_open(&digest_list_path, O_RDONLY, &init_cred);
+ if (IS_ERR(file)) {
+ pr_debug("Unable to open digest list %s\n", path_str);
+ goto out_unlock;
+ }
+
+ /* Write-lock the file to avoid getting outdated iint->flags. */
+ ret = deny_write_access(file);
+ if (ret < 0) {
+ pr_err("Unable to write-lock digest list %s", path_str);
+ goto out_fput;
+ }
+
+ ret = kernel_read_file(file, 0, &data, INT_MAX, NULL,
+ READING_DIGEST_LIST);
+ if (ret < 0) {
+ pr_debug("Unable to read digest list %s\n", path_str);
+ goto out_allow;
+ }
+
+ if (digest_list_iint->flags & IMA_MEASURED)
+ digest_cache_mask |= DIGEST_CACHE_MEASURE;
+ if (digest_list_iint->flags & IMA_APPRAISED_SUBMASK)
+ digest_cache_mask |= DIGEST_CACHE_APPRAISE_CONTENT;
+
+ if (!digest_cache_mask) {
+ pr_debug("No actions done on digest list %s\n", path_str);
+ ret = -ENOENT;
+ goto out_vfree;
+ }
+
+ data_len = ret;
+
+ digest_cache = digest_cache_alloc(path_str, digest_cache_mask);
+ if (!digest_cache)
+ goto out_vfree;
+
+ /* Freed by digest_cache_free(). */
+ path_str = NULL;
+
+ /*
+ * Digest list parsers must set the digest algorithm, initialize the
+ * hash table and add the digests.
+ */
+ ret = digest_cache_parse_digest_list(digest_cache, &digest_list_path,
+ data, data_len);
+ if (ret < 0) {
+ pr_debug("Error parsing digest list %s, ret: %d\n",
+ digest_cache->path_str, ret);
+ digest_cache_free(digest_cache);
+ digest_cache = NULL;
+ goto out_vfree;
+ }
+
+ digest_list_iint->dig_owner = digest_cache;
+
+ pr_debug("New cache (ref count: %d), algo: %s, digest list: %s, mask: %llu\n",
+ atomic_read(&digest_cache->ref_count),
+ hash_algo_name[digest_cache->algo], digest_cache->path_str,
+ digest_cache->mask);
+out_vfree:
+ vfree(data);
+out_allow:
+ allow_write_access(file);
+out_fput:
+ fput(file);
+out_unlock:
+ mutex_unlock(&digest_list_iint->dig_owner_mutex);
+out_path:
+ path_put(&digest_list_path);
+out:
+ kfree(path_str);
+ return digest_cache;
+}
+
+/**
+ * digest_cache_get - Obtain a digest cache and set it in the iint
+ * @dentry: Dentry of the file being measured/appraised
+ * @iint: Integrity inode cache of the file being measured/appraised
+ *
+ * Obtain a digest cache, and set it in the dig_user field of the passed iint,
+ * if not already done.
+ *
+ * Return: A digest cache on success, NULL otherwise.
+ */
+struct digest_cache *digest_cache_get(struct dentry *dentry,
+ struct integrity_iint_cache *iint)
+{
+ if (iint->dig_user)
+ return iint->dig_user;
+
+ mutex_lock(&iint->dig_user_mutex);
+ if (!iint->dig_user)
+ iint->dig_user = digest_cache_new(dentry);
+ mutex_unlock(&iint->dig_user_mutex);
+
+ return iint->dig_user;
+}
new file mode 100644
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2023 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ *
+ * Header of the integrity digest cache.
+ */
+
+#ifndef _DIGEST_CACHE_H
+#define _DIGEST_CACHE_H
+
+#include <linux/types.h>
+#include <linux/list.h>
+#include <crypto/hash_info.h>
+
+/* Depth if elements were uniformly distributed in the hash table slots. */
+#define DIGEST_CACHE_HTABLE_DEPTH 30
+
+/* There is no explicit concept of metadata measurement in IMA. */
+#define DIGEST_CACHE_MEASURE 0x01
+#define DIGEST_CACHE_APPRAISE_CONTENT 0x02
+
+struct integrity_iint_cache;
+
+/**
+ * struct digest_cache - Digest cache
+ * @slots: Hash table slots
+ * @num_slots: Number of slots
+ * @ref_count: Number of references to the digest cache
+ * @algo: Algorithm of digests stored in the cache
+ * @path_str: Path of the digest list the cache was created from
+ * @mask: For which IMA actions and purpose the digest cache can be used
+ *
+ * This structure represents a cache of digests extracted from a file, to be
+ * primarily used for IMA measurement and appraisal.
+ */
+struct digest_cache {
+ struct hlist_head *slots;
+ unsigned int num_slots;
+ atomic_t ref_count;
+ enum hash_algo algo;
+ char *path_str;
+ u64 mask;
+};
+
+/**
+ * struct digest_cache_entry - Entry of a digest cache
+ * @hnext: Pointer to the next element in the collision list
+ * @digest: Stored digest
+ *
+ * This structure represents an entry of a digest cache, storing a digest.
+ */
+struct digest_cache_entry {
+ struct hlist_node hnext;
+ u8 digest[];
+} __packed;
+
+static inline unsigned int digest_cache_hash_key(u8 *digest,
+ unsigned int num_slots)
+{
+ return (digest[0] | digest[1] << 8) % num_slots;
+}
+
+#ifdef CONFIG_INTEGRITY_DIGEST_CACHE
+void digest_cache_free(struct digest_cache *digest_cache);
+struct digest_cache *digest_cache_get(struct dentry *dentry,
+ struct integrity_iint_cache *iint);
+#else
+static inline void digest_cache_free(struct digest_cache *digest_cache)
+{
+}
+
+static inline struct digest_cache *
+digest_cache_get(struct dentry *dentry, struct integrity_iint_cache *iint)
+{
+ return NULL;
+}
+
+#endif /* CONFIG_INTEGRITY_DIGEST_CACHE */
+#endif /* _DIGEST_CACHE_H */
@@ -80,6 +80,12 @@ static void iint_free(struct integrity_iint_cache *iint)
iint->ima_creds_status = INTEGRITY_UNKNOWN;
iint->evm_status = INTEGRITY_UNKNOWN;
iint->measured_pcrs = 0;
+#ifdef CONFIG_INTEGRITY_DIGEST_CACHE
+ digest_cache_free(iint->dig_owner);
+ digest_cache_free(iint->dig_user);
+ iint->dig_owner = NULL;
+ iint->dig_user = NULL;
+#endif
kmem_cache_free(iint_cache, iint);
}
@@ -165,6 +171,12 @@ static void init_once(void *foo)
iint->ima_creds_status = INTEGRITY_UNKNOWN;
iint->evm_status = INTEGRITY_UNKNOWN;
mutex_init(&iint->mutex);
+#ifdef CONFIG_INTEGRITY_DIGEST_CACHE
+ iint->dig_owner = NULL;
+ iint->dig_user = NULL;
+ mutex_init(&iint->dig_owner_mutex);
+ mutex_init(&iint->dig_user_mutex);
+#endif
}
static int __init integrity_iintcache_init(void)
@@ -19,6 +19,8 @@
#include <linux/key.h>
#include <linux/audit.h>
+#include "digest_cache.h"
+
/* iint action cache flags */
#define IMA_MEASURE 0x00000001
#define IMA_MEASURED 0x00000002
@@ -171,6 +173,12 @@ struct integrity_iint_cache {
enum integrity_status ima_creds_status:4;
enum integrity_status evm_status:4;
struct ima_digest_data *ima_hash;
+#ifdef CONFIG_INTEGRITY_DIGEST_CACHE
+ struct digest_cache *dig_owner; /* created from this inode */
+ struct mutex dig_owner_mutex; /* protects dig_owner */
+ struct digest_cache *dig_user; /* user of the digest cache */
+ struct mutex dig_user_mutex; /* protects dig_user */
+#endif
};
/* rbtree tree calls to lookup, insert, delete