@@ -14,6 +14,7 @@
id(KEXEC_INITRAMFS, kexec-initramfs) \
id(POLICY, security-policy) \
id(X509_CERTIFICATE, x509-certificate) \
+ id(DIGEST_LIST, digest-list) \
id(MAX_ID, )
#define __fid_enumify(ENUM, dummy) READING_ ## ENUM,
@@ -4,4 +4,4 @@
obj-$(CONFIG_SECURITY_DIGEST_CACHE) += digest_cache.o
-digest_cache-y := main.o secfs.o htable.o
+digest_cache-y := main.o secfs.o htable.o populate.o modsig.o
@@ -15,6 +15,24 @@
/* Digest cache bits in flags. */
#define INIT_IN_PROGRESS 0 /* Digest cache being initialized. */
+#define INVALID 1 /* Digest cache marked as invalid. */
+
+/**
+ * struct read_work - Structure to schedule reading a digest list
+ * @work: Work structure
+ * @file: File descriptor of the digest list to read
+ * @data: Digest list data (updated)
+ * @ret: Return value from kernel_read_file() (updated)
+ *
+ * This structure contains the necessary information to schedule reading a
+ * digest list.
+ */
+struct read_work {
+ struct work_struct work;
+ struct file *file;
+ void *data;
+ int ret;
+};
/**
* struct digest_cache_entry - Entry of a digest cache hash table
@@ -127,4 +145,12 @@ int digest_cache_htable_lookup(struct dentry *dentry,
enum hash_algo algo);
void digest_cache_htable_free(struct digest_cache *digest_cache);
+/* populate.c */
+int digest_cache_populate(struct digest_cache *digest_cache,
+ struct path *digest_list_path, char *path_str,
+ char *filename);
+
+/* modsig.c */
+size_t digest_cache_strip_modsig(__u8 *data, size_t data_len);
+
#endif /* _DIGEST_CACHE_INTERNAL_H */
@@ -166,6 +166,17 @@ struct digest_cache *digest_cache_create(struct dentry *dentry,
set_bit(INIT_IN_PROGRESS, &dig_sec->dig_owner->flags);
mutex_unlock(&dig_sec->dig_owner_mutex);
+ if (S_ISREG(inode->i_mode)) {
+ ret = digest_cache_populate(digest_cache, digest_list_path,
+ path_str, filename);
+ if (ret < 0) {
+ pr_debug("Failed to populate digest cache %s ret: %d (keep digest cache)\n",
+ digest_cache->path_str, ret);
+ /* Prevent usage of partially-populated digest cache. */
+ set_bit(INVALID, &digest_cache->flags);
+ }
+ }
+
/* Creation complete, notify the other lock contenders. */
clear_and_wake_up_bit(INIT_IN_PROGRESS, &dig_sec->dig_owner->flags);
exists:
@@ -173,6 +184,13 @@ struct digest_cache *digest_cache_create(struct dentry *dentry,
/* Wait until creation complete. */
wait_on_bit(&dig_sec->dig_owner->flags, INIT_IN_PROGRESS,
TASK_UNINTERRUPTIBLE);
+
+ if (test_bit(INVALID, &digest_cache->flags)) {
+ pr_debug("Digest cache %s is invalid, don't return it\n",
+ digest_cache->path_str);
+ digest_cache_put(digest_cache);
+ digest_cache = NULL;
+ }
out:
if (digest_list_path == &file_path)
path_put(&file_path);
new file mode 100644
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved.
+ * Copyright (C) 2019 IBM Corporation
+ * Copyright (C) 2023-2024 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ *
+ * Strip module-style appended signatures.
+ */
+
+#define pr_fmt(fmt) "DIGEST CACHE: "fmt
+#include <linux/module.h>
+#include <linux/module_signature.h>
+
+#include "internal.h"
+
+/**
+ * digest_cache_strip_modsig - Strip module-style appended sig from digest list
+ * @data: Data to parse
+ * @data_len: Length of @data
+ *
+ * This function strips the module-style appended signature from a digest list,
+ * if present.
+ *
+ * Return: Size of stripped data on success, original size otherwise.
+ */
+size_t digest_cache_strip_modsig(__u8 *data, size_t data_len)
+{
+ const size_t marker_len = strlen(MODULE_SIG_STRING);
+ const struct module_signature *sig;
+ size_t parsed_data_len = data_len;
+ size_t sig_len;
+ const void *p;
+
+ /* From ima_modsig.c */
+ if (data_len <= marker_len + sizeof(*sig))
+ return data_len;
+
+ p = data + parsed_data_len - marker_len;
+ if (memcmp(p, MODULE_SIG_STRING, marker_len))
+ return data_len;
+
+ parsed_data_len -= marker_len;
+ sig = (const struct module_signature *)(p - sizeof(*sig));
+
+ /* From module_signature.c */
+ if (be32_to_cpu(sig->sig_len) >= parsed_data_len - sizeof(*sig))
+ return data_len;
+
+ /* Unlike for module signatures, accept all signature types. */
+ if (sig->algo != 0 ||
+ sig->hash != 0 ||
+ sig->signer_len != 0 ||
+ sig->key_id_len != 0 ||
+ sig->__pad[0] != 0 ||
+ sig->__pad[1] != 0 ||
+ sig->__pad[2] != 0) {
+ pr_debug("Signature info has unexpected non-zero params\n");
+ return data_len;
+ }
+
+ sig_len = be32_to_cpu(sig->sig_len);
+ parsed_data_len -= sig_len + sizeof(*sig);
+ return parsed_data_len;
+}
new file mode 100644
@@ -0,0 +1,149 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2023-2024 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ *
+ * Implement the code to populate a digest cache.
+ */
+
+#define pr_fmt(fmt) "DIGEST CACHE: "fmt
+#include <linux/init_task.h>
+#include <linux/kernel_read_file.h>
+
+#include "internal.h"
+
+/**
+ * digest_cache_parse_digest_list - Parse a digest list
+ * @digest_cache: Digest cache
+ * @path_str: Path string of the digest list
+ * @filename: Digest list file name (can be an empty string)
+ * @data: Data to parse
+ * @data_len: Length of @data
+ *
+ * This function selects a parser for a digest list depending on its file name,
+ * and calls the appropriate parsing function. It expects the file name to be
+ * in the format: [<seq num>-]<format>-<digest list name>. <seq num> is
+ * optional.
+ *
+ * Return: Zero on success, a POSIX error code otherwise.
+ */
+static int digest_cache_parse_digest_list(struct digest_cache *digest_cache,
+ char *path_str, char *filename,
+ void *data, size_t data_len)
+{
+ char *format, *next_sep;
+ int ret = -EINVAL;
+
+ if (!filename[0]) {
+ filename = strrchr(path_str, '/');
+ if (!filename)
+ return ret;
+
+ filename++;
+ }
+
+ format = filename;
+
+ /*
+ * Since we expect that all files start with a digest list format, this
+ * check is reliable to detect <seq num>.
+ */
+ if (filename[0] >= '0' && filename[0] <= '9') {
+ format = strchr(filename, '-');
+ if (!format)
+ return ret;
+
+ format++;
+ }
+
+ next_sep = strchr(format, '-');
+ if (!next_sep)
+ return ret;
+
+ pr_debug("Parsing %s%s%s, format: %.*s, size: %ld\n", path_str,
+ filename[0] ? "/" : "", filename, (int)(next_sep - format),
+ format, data_len);
+
+ return ret;
+}
+
+/**
+ * digest_cache_read_digest_list - Read a digest list
+ * @work: Work structure
+ *
+ * This function is invoked by schedule_work() to read a digest list.
+ *
+ * It does not return a value, but stores the result in the passed structure.
+ */
+static void digest_cache_read_digest_list(struct work_struct *work)
+{
+ struct read_work *w = container_of(work, struct read_work, work);
+
+ w->ret = kernel_read_file(w->file, 0, &w->data, INT_MAX, NULL,
+ READING_DIGEST_LIST);
+}
+
+/**
+ * digest_cache_populate - Populate a digest cache from a digest list
+ * @digest_cache: Digest cache
+ * @digest_list_path: Path structure of the digest list
+ * @path_str: Path string of the digest list
+ * @filename: Digest list file name (can be an empty string)
+ *
+ * This function opens the digest list for reading it. Then, it schedules a
+ * work to read the digest list and, once the work is done, it calls
+ * digest_cache_strip_modsig() to strip a module-style appended signature and
+ * digest_cache_parse_digest_list() for extracting and adding digests to the
+ * digest cache.
+ *
+ * Return: Zero on success, a POSIX error code otherwise.
+ */
+int digest_cache_populate(struct digest_cache *digest_cache,
+ struct path *digest_list_path, char *path_str,
+ char *filename)
+{
+ struct file *file;
+ void *data;
+ size_t data_len;
+ struct read_work w;
+ int ret;
+
+ file = dentry_open(digest_list_path, O_RDONLY, &init_cred);
+ if (IS_ERR(file)) {
+ pr_debug("Unable to open digest list %s%s%s, ret: %ld\n",
+ path_str, filename[0] ? "/" : "", filename,
+ PTR_ERR(file));
+ return PTR_ERR(file);
+ }
+
+ w.data = NULL;
+ w.file = file;
+ INIT_WORK_ONSTACK(&w.work, digest_cache_read_digest_list);
+
+ schedule_work(&w.work);
+ flush_work(&w.work);
+ destroy_work_on_stack(&w.work);
+ fput(file);
+
+ ret = w.ret;
+ data = w.data;
+
+ if (ret < 0) {
+ pr_debug("Unable to read digest list %s%s%s, ret: %d\n",
+ path_str, filename[0] ? "/" : "", filename, ret);
+ return ret;
+ }
+
+ data_len = digest_cache_strip_modsig(data, ret);
+
+ /* Digest list parsers initialize the hash table and add the digests. */
+ ret = digest_cache_parse_digest_list(digest_cache, path_str, filename,
+ data, data_len);
+ if (ret < 0)
+ pr_debug("Error parsing digest list %s%s%s, ret: %d\n",
+ path_str, filename[0] ? "/" : "", filename, ret);
+
+ vfree(data);
+ return ret;
+}