diff mbox series

[RFC,v2,04/13] integrity/digest_cache: Prefetch digest lists in a directory

Message ID 20230812104616.2190095-5-roberto.sassu@huaweicloud.com (mailing list archive)
State New, archived
Headers show
Series integrity: Introduce a digest cache | expand

Commit Message

Roberto Sassu Aug. 12, 2023, 10:46 a.m. UTC
From: Roberto Sassu <roberto.sassu@huawei.com>

Prefetch the digest lists in the same directory as the requested one, to
measure them in a deterministic way, and obtain a predictable PCR.

Prefetching does not imply parsing, so there won't be significant memory
consumption. However, since all PCR extend operations are done all at
the same time, this iteration is expected to introduce a noticeable delay
to the first file operation for which the digest cache is used.

This patch assumes for now that all digest lists are stored in the same
directory.

Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
 security/integrity/Makefile            |   3 +-
 security/integrity/digest_cache.h      |   5 +
 security/integrity/digest_cache_iter.c | 160 +++++++++++++++++++++++++
 3 files changed, 167 insertions(+), 1 deletion(-)
 create mode 100644 security/integrity/digest_cache_iter.c
diff mbox series

Patch

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 01cd70f9850..cd5c913cf7a 100644
--- a/security/integrity/digest_cache.h
+++ b/security/integrity/digest_cache.h
@@ -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 */
diff --git a/security/integrity/digest_cache_iter.c b/security/integrity/digest_cache_iter.c
new file mode 100644
index 00000000000..f7641e7b365
--- /dev/null
+++ b/security/integrity/digest_cache_iter.c
@@ -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);
+}