diff --git a/include/uapi/linux/xattr.h b/include/uapi/linux/xattr.h
index 9463db2dfa9..8a58cf4bce6 100644
--- a/include/uapi/linux/xattr.h
+++ b/include/uapi/linux/xattr.h
@@ -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
 
diff --git a/security/integrity/Kconfig b/security/integrity/Kconfig
index ec6e0d789da..df8a1f7e6e2 100644
--- a/security/integrity/Kconfig
+++ b/security/integrity/Kconfig
@@ -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"
 
diff --git a/security/integrity/Makefile b/security/integrity/Makefile
index d0ffe37dc1d..0c175a567ac 100644
--- a/security/integrity/Makefile
+++ b/security/integrity/Makefile
@@ -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
diff --git a/security/integrity/digest_cache.c b/security/integrity/digest_cache.c
new file mode 100644
index 00000000000..66c2c4088e9
--- /dev/null
+++ b/security/integrity/digest_cache.c
@@ -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);
+}
diff --git a/security/integrity/digest_cache.h b/security/integrity/digest_cache.h
new file mode 100644
index 00000000000..fa4a716df65
--- /dev/null
+++ b/security/integrity/digest_cache.h
@@ -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 */
diff --git a/security/integrity/iint.c b/security/integrity/iint.c
index a462df827de..9a35ae1fb85 100644
--- a/security/integrity/iint.c
+++ b/security/integrity/iint.c
@@ -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)
diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h
index 3aef3d8fb57..859a94bcecb 100644
--- a/security/integrity/ima/ima.h
+++ b/security/integrity/ima/ima.h
@@ -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 */
diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h
index 7167a6e99bd..bd3f9a27f0d 100644
--- a/security/integrity/integrity.h
+++ b/security/integrity/integrity.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
