@@ -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,300 @@
+// 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
+ *
+ * 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)
+{
+ 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 = 0;
+ digest_cache->slots = NULL;
+ digest_cache->num_slots = 0;
+ 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;
+
+ 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("Free cache, algo: %s, digest list: %s",
+ 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_get - Get a digest cache
+ * @dentry_to_check: Dentry of the file being measured/appraised
+ * @digest_list_path: Path structure of the digest list
+ *
+ * This function retrieves the path of the digest list from the
+ * security.digest_list xattr of the file being measured/appraised. It then
+ * instantiates a new digest cache, and opens and parses the digest list.
+ *
+ * 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.
+ *
+ * The caller is responsible to invoke digest_cache_put(), to release
+ * the reference of the path structure associated to the digest list.
+ *
+ * Return: A new digest cache on success, NULL on error.
+ */
+struct digest_cache *digest_cache_get(struct dentry *dentry_to_check,
+ struct path *digest_list_path)
+{
+ struct integrity_iint_cache *digest_list_iint;
+ struct digest_cache *digest_cache = NULL;
+ char *path_str = NULL;
+ struct file *file;
+ void *data = NULL;
+ size_t data_len = 0;
+ struct inode *inode;
+ int ret;
+
+ ret = vfs_getxattr_alloc(&nop_mnt_idmap, dentry_to_check,
+ 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_to_check->d_name.name);
+ return digest_cache;
+ }
+
+ pr_debug("Found %s xattr in %s, digest list: %s\n",
+ XATTR_NAME_DIGEST_LIST, dentry_to_check->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->digest_cache) {
+ pr_debug("Cache for digest list %s exists\n", path_str);
+ digest_cache = digest_list_iint->digest_cache;
+ goto out_path;
+ }
+
+ 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_path;
+ }
+
+ /* 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;
+ }
+
+ data_len = ret;
+
+ digest_cache = digest_cache_alloc(path_str);
+ 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;
+ }
+
+ /*
+ * Add penalty only for concurrent add, otherwise don't take a lock.
+ * In the worst case, the lock contenders waste time to create a
+ * digest cache that is freed.
+ */
+ mutex_lock(&digest_list_iint->mutex);
+ /* Someone came before us. */
+ if (digest_list_iint->digest_cache) {
+ pr_debug("Cache for digest list %s exists\n",
+ digest_cache->path_str);
+ digest_cache_free(digest_cache);
+ digest_cache = digest_list_iint->digest_cache;
+ mutex_unlock(&digest_list_iint->mutex);
+ goto out_vfree;
+ }
+
+ digest_list_iint->digest_cache = digest_cache;
+ 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;
+ mutex_unlock(&digest_list_iint->mutex);
+
+ pr_debug("Get cache, algo: %s, digest list: %s, mask: %d\n",
+ 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_path:
+ if (!digest_cache)
+ path_put(digest_list_path);
+out:
+ kfree(path_str);
+ return digest_cache;
+}
+
+/**
+ * digest_cache_put - Release a digest cache
+ * @digest_cache: Digest cache
+ * @digest_list_path: Path structure of the digest list
+ *
+ * This function releases the path structure of the digest list.
+ */
+void digest_cache_put(struct digest_cache *digest_cache,
+ struct path *digest_list_path)
+{
+ if (!digest_cache)
+ return;
+
+ /* Pairs with kernel_path() in digest_cache_get(). */
+ path_put(digest_list_path);
+
+ pr_debug("Put cache, algo: %s, digest list: %s",
+ hash_algo_name[digest_cache->algo], digest_cache->path_str);
+}
new file mode 100644
@@ -0,0 +1,84 @@
+/* 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 digest_cache - Digest cache
+ * @slots: Hash table slots
+ * @num_slots: Number of slots
+ * @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;
+ enum hash_algo algo;
+ char *path_str;
+ u8 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 path *digest_list_path);
+void digest_cache_put(struct digest_cache *digest_cache,
+ struct path *digest_list_path);
+#else
+static inline void digest_cache_free(struct digest_cache *digest_cache)
+{
+}
+
+static inline struct digest_cache *
+digest_cache_get(struct dentry *dentry, struct path *digest_list_path)
+{
+ return ERR_PTR(-EOPNOTSUPP);
+}
+
+static inline void digest_cache_put(struct digest_cache *digest_cache,
+ struct path *digest_list_path)
+{
+}
+
+#endif /* CONFIG_INTEGRITY_DIGEST_CACHE */
+#endif /* _DIGEST_CACHE_H */
@@ -80,6 +80,10 @@ 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->digest_cache);
+ iint->digest_cache = NULL;
+#endif
kmem_cache_free(iint_cache, iint);
}
@@ -165,6 +169,9 @@ 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->digest_cache = NULL;
+#endif
}
static int __init integrity_iintcache_init(void)
@@ -452,5 +452,4 @@ static inline int ima_filter_rule_match(u32 secid, u32 field, u32 op,
#else
#define POLICY_FILE_FLAGS S_IWUSR
#endif /* CONFIG_IMA_READ_POLICY */
-
#endif /* __LINUX_IMA_H */
@@ -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,9 @@ 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 *digest_cache;
+#endif
};
/* rbtree tree calls to lookup, insert, delete