@@ -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
@@ -71,6 +71,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)
{
@@ -101,5 +102,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 */
new file mode 100644
@@ -0,0 +1,160 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2023 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ *
+ * Implement a digest list iterator.
+ */
+
+#include <linux/fs.h>
+#include <linux/xattr.h>
+#include <linux/vmalloc.h>
+#include <linux/kernel_read_file.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_tail(&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, *dir_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;
+
+ dir_path_str = kstrndup(path_str, ptr - path_str, GFP_KERNEL);
+ if (!dir_path_str)
+ goto out;
+
+ dir_file = filp_open(dir_path_str, O_RDONLY, 0);
+
+ if (IS_ERR(dir_file)) {
+ pr_debug("Cannot access %s\n", dir_path_str);
+ goto out;
+ }
+
+ buf.head = &head;
+ iterate_dir(dir_file, &buf.ctx);
+ list_for_each_entry_safe(p, q, &head, list) {
+ pr_debug("Prefetching digest list %s/%s\n", dir_path_str,
+ p->name);
+
+ 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);
+ kfree(dir_path_str);
+}