[RFC][PATCH 04/12] integrity/digest_cache: Iterate over digest lists in same dir

From: Roberto Sassu
Date: Fri Jul 21 2023 - 12:35:56 EST


From: Roberto Sassu <roberto.sassu@xxxxxxxxxx>

One advantage of the digest cache is the ability of skipping measurements
of cached digests. That would allow to accumulate integrity measurements
on a PCR in a predictable way, since only the digest lists would be
measured.

However, since digest lists are accessed on demand, when a file belonging
to that repo is measured/appraised, it could happen due to parallel
execution that also digest lists are measured not in the same order.

Thus, eliminate this possibility by iterating over the directory containing
the digest lists and by reading all of them, to trigger a measurement.

Read digest lists are not parsed, to avoid too much memory pressure.

Signed-off-by: Roberto Sassu <roberto.sassu@xxxxxxxxxx>
---
security/integrity/Makefile | 3 +-
security/integrity/digest_cache.h | 5 +
security/integrity/digest_cache_iter.c | 163 +++++++++++++++++++++++++
3 files changed, 170 insertions(+), 1 deletion(-)
create mode 100644 security/integrity/digest_cache_iter.c

diff --git a/security/integrity/Makefile b/security/integrity/Makefile
index 0c175a567ac..c856ed10fba 100644
--- a/security/integrity/Makefile
+++ b/security/integrity/Makefile
@@ -11,7 +11,8 @@ 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_INTEGRITY_DIGEST_CACHE) += digest_cache.o \
+ digest_cache_iter.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.h b/security/integrity/digest_cache.h
index 5e3997b2723..d8fd5ce47a7 100644
--- a/security/integrity/digest_cache.h
+++ b/security/integrity/digest_cache.h
@@ -69,6 +69,7 @@ int digest_cache_init_htable(struct digest_cache *digest_cache,
int digest_cache_add(struct digest_cache *digest_cache, u8 *digest);
int digest_cache_lookup(struct digest_cache *digest_cache, u8 *digest,
enum hash_algo algo, const char *pathname);
+void digest_cache_iter_dir(struct dentry *repo_dentry);
#else
static inline void digest_cache_free(struct digest_cache *digest_cache)
{
@@ -104,5 +105,9 @@ static inline int digest_cache_lookup(struct digest_cache *digest_cache,
return -ENOENT;
}

+static inline void digest_cache_iter_dir(struct dentry *repo_dentry)
+{
+}
+
#endif /* CONFIG_INTEGRITY_DIGEST_CACHE */
#endif /* _DIGEST_CACHE_H */
diff --git a/security/integrity/digest_cache_iter.c b/security/integrity/digest_cache_iter.c
new file mode 100644
index 00000000000..f9c4675d383
--- /dev/null
+++ b/security/integrity/digest_cache_iter.c
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 IBM Corporation
+ * Copyright (C) 2023 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@xxxxxxxxxx>
+ *
+ * Implement a digest list iterator.
+ */
+
+#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 ITER: "fmt
+
+static bool iterated;
+/* Ensure there is only one iteration over digest lists, make others wait. */
+DEFINE_MUTEX(iterate_mutex);
+
+struct dir_entry {
+ struct list_head list;
+ char name[];
+} __packed;
+
+struct readdir_callback {
+ struct dir_context ctx;
+ struct list_head *head;
+};
+
+/**
+ * digest_cache_iter_digest_list - Callback func to get digest lists in a dir
+ * @__ctx: iterate_dir() context
+ * @name: Name of file in the accessed dir
+ * @namelen: String length of @name
+ * @offset: Current position in the directory stream (see man readdir)
+ * @ino: Inode number
+ * @d_type: File type
+ *
+ * This function stores the names of the files in the containing directory in
+ * a linked list. Those files will be opened to trigger a measurement.
+ *
+ * Return: True to continue processing, false to stop.
+ */
+static bool digest_cache_iter_digest_list(struct dir_context *__ctx,
+ const char *name, int namelen,
+ loff_t offset, u64 ino,
+ unsigned int d_type)
+{
+ struct readdir_callback *ctx = container_of(__ctx, typeof(*ctx), ctx);
+ struct dir_entry *new_entry;
+
+ if (!strcmp(name, ".") || !strcmp(name, ".."))
+ return true;
+
+ if (d_type != DT_REG)
+ return true;
+
+ new_entry = kmalloc(sizeof(*new_entry) + namelen + 1, GFP_KERNEL);
+ if (!new_entry)
+ return true;
+
+ memcpy(new_entry->name, name, namelen);
+ new_entry->name[namelen] = '\0';
+ list_add(&new_entry->list, ctx->head);
+ return true;
+}
+
+/**
+ * digest_cache_iter_dir - Iterate over all files in the same digest list dir
+ * @digest_list_dentry: Digest list dentry
+ *
+ * This function iterates over all files in the directory containing the digest
+ * list provided as argument. It helps to measure digest lists in a
+ * deterministic order and make a TPM PCR predictable.
+ */
+void digest_cache_iter_dir(struct dentry *digest_list_dentry)
+{
+ struct file *dir_file;
+ struct readdir_callback buf = {
+ .ctx.actor = digest_cache_iter_digest_list,
+ };
+ struct dir_entry *p, *q;
+ struct file *file;
+ char *path_str = NULL;
+ void *data;
+ LIST_HEAD(head);
+ char *ptr;
+ int ret;
+
+ if (iterated)
+ return;
+
+ mutex_lock(&iterate_mutex);
+ if (iterated)
+ goto out;
+
+ iterated = true;
+
+ ret = vfs_getxattr_alloc(&nop_mnt_idmap, digest_list_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,
+ digest_list_dentry->d_name.name);
+ goto out;
+ }
+
+ pr_debug("Found %s xattr in %s, digest list: %s\n",
+ XATTR_NAME_DIGEST_LIST, digest_list_dentry->d_name.name,
+ path_str);
+
+ ptr = strrchr(path_str, '/');
+ if (!ptr)
+ goto out;
+
+ *ptr = '\0';
+ dir_file = filp_open(path_str, O_RDONLY, 0);
+ *ptr = '/';
+
+ if (IS_ERR(dir_file)) {
+ pr_debug("Cannot access parent directory of repo %s\n",
+ path_str);
+ goto out;
+ }
+
+ buf.head = &head;
+ iterate_dir(dir_file, &buf.ctx);
+ list_for_each_entry_safe(p, q, &head, list) {
+ pr_debug("Prereading digest list %s in %s\n", p->name,
+ path_str);
+
+ file = file_open_root(&dir_file->f_path, p->name, O_RDONLY, 0);
+ if (IS_ERR(file))
+ continue;
+
+ data = NULL;
+
+ ret = kernel_read_file(file, 0, &data, INT_MAX, NULL,
+ READING_DIGEST_LIST);
+ if (ret >= 0)
+ vfree(data);
+
+ fput(file);
+ list_del(&p->list);
+ kfree(p);
+ }
+
+ fput(dir_file);
+out:
+ mutex_unlock(&iterate_mutex);
+ kfree(path_str);
+}
--
2.34.1