[PATCH v6 07/15] digest_cache: Allow registration of digest list parsers
Roberto Sassu
roberto.sassu at huaweicloud.com
Tue Nov 19 10:49:14 UTC 2024
From: Roberto Sassu <roberto.sassu at huawei.com>
Allow kernel modules to register/deregister new digest list parsers,
respectively through digest_cache_register_parser() and
digest_cache_unregister_parser().
Those functions pass the new parser structure holding the linked list
pointers and a parsing function with the new type parser_func.
Introduce digest_cache_parse_digest_list(), which determines the desired
parser from the file name, looks up the parser among the registered ones
with lookup_get_parser(), calls the parser-specific function along with the
digest list data read by the kernel, and finally releases the kernel module
reference with put_parser().
The expected digest list file name format is:
[<seq num>-]<digest list format>-<digest list name>
<seq-num>- is an optional prefix to impose in which order digest lists in
a directory should be parsed.
Introduce load_parser() to load a kernel module containing a
parser for the requested digest list format (compressed kernel modules are
supported). Kernel modules are searched in the
/lib/modules/<kernel ver>/security/integrity/digest_cache directory.
load_parser() calls ksys_finit_module() to load a kernel module directly
from the kernel. request_module() cannot be used at this point, since the
reference digests of modprobe and the linked libraries (required for IMA
appraisal) might not be yet available, resulting in modprobe execution
being denied.
Signed-off-by: Roberto Sassu <roberto.sassu at huawei.com>
---
include/linux/digest_cache.h | 38 +++
security/integrity/digest_cache/Kconfig | 1 +
security/integrity/digest_cache/Makefile | 4 +-
security/integrity/digest_cache/internal.h | 5 +
security/integrity/digest_cache/parsers.c | 257 +++++++++++++++++++++
5 files changed, 304 insertions(+), 1 deletion(-)
create mode 100644 security/integrity/digest_cache/parsers.c
diff --git a/include/linux/digest_cache.h b/include/linux/digest_cache.h
index 59a42c04cbb8..a9d731990b7c 100644
--- a/include/linux/digest_cache.h
+++ b/include/linux/digest_cache.h
@@ -13,6 +13,32 @@
#include <linux/fs.h>
#include <crypto/hash_info.h>
+struct digest_cache;
+
+/**
+ * typedef parser_func - Function to parse digest lists
+ *
+ * Define a function type to parse digest lists.
+ */
+typedef int (*parser_func)(struct digest_cache *digest_cache, const u8 *data,
+ size_t data_len);
+
+/**
+ * struct parser - Structure to store a function pointer to parse digest list
+ * @list: Linked list
+ * @owner: Kernel module owning the parser
+ * @name: Parser name (must match the format in the digest list file name)
+ * @func: Function pointer for parsing
+ *
+ * This structure stores a function pointer to parse a digest list.
+ */
+struct parser {
+ struct list_head list;
+ struct module *owner;
+ const char name[NAME_MAX + 1];
+ parser_func func;
+};
+
#ifdef CONFIG_INTEGRITY_DIGEST_CACHE
/* Client API */
struct digest_cache *digest_cache_get(struct file *file);
@@ -30,6 +56,8 @@ int digest_cache_htable_add(struct digest_cache *digest_cache, u8 *digest,
int digest_cache_htable_lookup(struct dentry *dentry,
struct digest_cache *digest_cache, u8 *digest,
enum hash_algo algo);
+int digest_cache_register_parser(struct parser *parser);
+void digest_cache_unregister_parser(struct parser *parser);
#else
static inline struct digest_cache *digest_cache_get(struct file *file)
@@ -72,5 +100,15 @@ static inline int digest_cache_htable_lookup(struct dentry *dentry,
return -EOPNOTSUPP;
}
+static inline int digest_cache_register_parser(const char *name,
+ parser_func func)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline void digest_cache_unregister_parser(const char *name)
+{
+}
+
#endif /* CONFIG_INTEGRITY_DIGEST_CACHE */
#endif /* _LINUX_DIGEST_CACHE_H */
diff --git a/security/integrity/digest_cache/Kconfig b/security/integrity/digest_cache/Kconfig
index 419011fb52c9..65c07110911b 100644
--- a/security/integrity/digest_cache/Kconfig
+++ b/security/integrity/digest_cache/Kconfig
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
config INTEGRITY_DIGEST_CACHE
bool "Integrity Digest Cache"
+ select MODULE_DECOMPRESS if MODULE_COMPRESS
default n
help
This option enables a cache of reference digests (e.g. of file
diff --git a/security/integrity/digest_cache/Makefile b/security/integrity/digest_cache/Makefile
index 0092c913979d..d68cae690241 100644
--- a/security/integrity/digest_cache/Makefile
+++ b/security/integrity/digest_cache/Makefile
@@ -4,4 +4,6 @@
obj-$(CONFIG_INTEGRITY_DIGEST_CACHE) += digest_cache.o
-digest_cache-y := main.o secfs.o htable.o
+digest_cache-y := main.o secfs.o htable.o parsers.o
+
+CFLAGS_parsers.o += -DPARSERS_DIR=\"$(MODLIB)/kernel/security/integrity/digest_cache/parsers\"
diff --git a/security/integrity/digest_cache/internal.h b/security/integrity/digest_cache/internal.h
index e14343e96caa..e178549f9ff9 100644
--- a/security/integrity/digest_cache/internal.h
+++ b/security/integrity/digest_cache/internal.h
@@ -161,4 +161,9 @@ int __init digest_cache_secfs_init(struct dentry *dir);
/* htable.c */
void digest_cache_htable_free(struct digest_cache *digest_cache);
+/* parsers.c */
+int digest_cache_parse_digest_list(struct dentry *dentry,
+ struct digest_cache *digest_cache,
+ char *path_str, void *data, size_t data_len);
+
#endif /* _DIGEST_CACHE_INTERNAL_H */
diff --git a/security/integrity/digest_cache/parsers.c b/security/integrity/digest_cache/parsers.c
new file mode 100644
index 000000000000..744c9742a44e
--- /dev/null
+++ b/security/integrity/digest_cache/parsers.c
@@ -0,0 +1,257 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2024 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu at huawei.com>
+ *
+ * Implement the code to register digest list parsers.
+ */
+
+#define pr_fmt(fmt) "digest_cache: "fmt
+#include <linux/init_task.h>
+#include <linux/namei.h>
+#include <uapi/linux/module.h>
+#include <linux/syscalls.h>
+#include <linux/vmalloc.h>
+
+#include "internal.h"
+
+static DEFINE_MUTEX(parsers_mutex);
+static LIST_HEAD(parsers);
+
+/**
+ * load_parser - Load kernel module containing a parser
+ * @dentry: Dentry of the inode for which the digest cache will be used
+ * @digest_cache: Digest cache
+ * @name: Name of the parser to load
+ *
+ * This function opens a kernel module file in
+ * /lib/modules/<kernel ver>/security/integrity/digest_cache, and executes
+ * ksys_finit_module() to load the kernel module. After kernel module
+ * initialization, the parser should be found in the linked list of parsers.
+ *
+ * Return: Zero if kernel module is loaded, a POSIX error code otherwise.
+ */
+static int load_parser(struct dentry *dentry, struct digest_cache *digest_cache,
+ const char *name)
+{
+ char *compress_suffix = "";
+ char *parser_path;
+ struct file *file;
+ struct path path;
+ int ret = 0, flags = 0;
+
+ /* Must be kept in sync with kernel/module/Kconfig. */
+ if (IS_ENABLED(CONFIG_MODULE_COMPRESS_GZIP))
+ compress_suffix = ".gz";
+ else if (IS_ENABLED(CONFIG_MODULE_COMPRESS_XZ))
+ compress_suffix = ".xz";
+ else if (IS_ENABLED(CONFIG_MODULE_COMPRESS_ZSTD))
+ compress_suffix = ".zst";
+
+ if (strlen(compress_suffix))
+ flags |= MODULE_INIT_COMPRESSED_FILE;
+
+ parser_path = kasprintf(GFP_KERNEL, "%s/%s.ko%s", PARSERS_DIR, name,
+ compress_suffix);
+ if (!parser_path)
+ return -ENOMEM;
+
+ ret = kern_path(parser_path, 0, &path);
+ if (ret < 0) {
+ pr_debug("Cannot find path %s\n", parser_path);
+ goto out;
+ }
+
+ /* Cannot request a digest cache for the kernel module inode. */
+ if (d_backing_inode(dentry) == d_backing_inode(path.dentry)) {
+ pr_debug("Cannot request a digest cache for kernel module %s\n",
+ dentry->d_name.name);
+ ret = -EBUSY;
+ goto out;
+ }
+
+ file = kernel_file_open(&path, O_RDONLY, &init_cred);
+ if (IS_ERR(file)) {
+ pr_debug("Cannot open %s\n", parser_path);
+ ret = PTR_ERR(file);
+ goto out_path;
+ }
+
+ /* Mark the file descriptor as ours. */
+ digest_cache_to_file_sec(file, digest_cache);
+
+ ret = ksys_finit_module(file, "", flags);
+ if (ret < 0)
+ pr_debug("Cannot load module %s\n", parser_path);
+
+ fput(file);
+out_path:
+ path_put(&path);
+out:
+ kfree(parser_path);
+ return ret;
+}
+
+/**
+ * lookup_get_parser - Lookup and get parser among registered ones
+ * @name: Name of the parser to search
+ *
+ * This function searches a parser among the registered ones, and returns it
+ * to the caller, after incrementing the kernel module reference count.
+ *
+ * Must be called with parser_mutex held.
+ *
+ * Return: A parser structure if parser is found and available, NULL otherwise.
+ */
+static struct parser *lookup_get_parser(const char *name)
+{
+ struct parser *entry, *found = NULL;
+
+ list_for_each_entry(entry, &parsers, list) {
+ if (!strcmp(entry->name, name) &&
+ try_module_get(entry->owner)) {
+ found = entry;
+ break;
+ }
+ }
+
+ return found;
+}
+
+/**
+ * put_parser - Put parser
+ * @parser: Parser to put
+ *
+ * This function decreases the kernel module reference count.
+ */
+static void put_parser(struct parser *parser)
+{
+ module_put(parser->owner);
+}
+
+/**
+ * digest_cache_parse_digest_list - Parse a digest list
+ * @dentry: Dentry of the inode for which the digest cache will be used
+ * @digest_cache: Digest cache
+ * @path_str: Path string of the digest list
+ * @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>-]<digest list format>-<digest list name>.
+ * <seq num>- is optional.
+ *
+ * Return: Zero on success, a POSIX error code otherwise.
+ */
+int digest_cache_parse_digest_list(struct dentry *dentry,
+ struct digest_cache *digest_cache,
+ char *path_str, void *data, size_t data_len)
+{
+ char *filename, *format, *next_sep;
+ struct parser *parser;
+ char format_buf[sizeof(parser->name)];
+ int ret = -EINVAL;
+
+ 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 || next_sep - format >= sizeof(format_buf))
+ return ret;
+
+ snprintf(format_buf, sizeof(format_buf), "%.*s",
+ (int)(next_sep - format), format);
+
+ pr_debug("Parsing %s, format: %s, size: %ld\n", path_str, format_buf,
+ data_len);
+
+ mutex_lock(&parsers_mutex);
+ parser = lookup_get_parser(format_buf);
+ mutex_unlock(&parsers_mutex);
+
+ if (!parser) {
+ load_parser(dentry, digest_cache, format_buf);
+
+ mutex_lock(&parsers_mutex);
+ parser = lookup_get_parser(format_buf);
+ mutex_unlock(&parsers_mutex);
+
+ if (!parser) {
+ pr_debug("Digest list parser %s not found\n",
+ format_buf);
+ return -ENOENT;
+ }
+ }
+
+ ret = parser->func(digest_cache, data, data_len);
+ put_parser(parser);
+
+ return ret;
+}
+
+/**
+ * digest_cache_register_parser - Register new parser
+ * @parser: Parser structure to register
+ *
+ * This function searches the parser name among the registered ones and, if not
+ * found, appends the parser to the linked list of parsers.
+ *
+ * Return: Zero on success, -EEXIST if a parser with the same name exists.
+ */
+int digest_cache_register_parser(struct parser *parser)
+{
+ struct parser *p;
+ int ret = 0;
+
+ mutex_lock(&parsers_mutex);
+ p = lookup_get_parser(parser->name);
+ if (p) {
+ put_parser(p);
+ ret = -EEXIST;
+ goto out;
+ }
+
+ list_add_tail(&parser->list, &parsers);
+out:
+ pr_debug("Digest list parser \'%s\' %s registered\n", parser->name,
+ (ret < 0) ? "cannot be" : "successfully");
+
+ mutex_unlock(&parsers_mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(digest_cache_register_parser);
+
+/**
+ * digest_cache_unregister_parser - Unregister parser
+ * @parser: Parser structure to unregister
+ *
+ * This function removes the passed parser from the linked list of parsers.
+ */
+void digest_cache_unregister_parser(struct parser *parser)
+{
+ mutex_lock(&parsers_mutex);
+ list_del(&parser->list);
+ mutex_unlock(&parsers_mutex);
+
+ pr_debug("Digest list parser \'%s\' successfully unregistered\n",
+ parser->name);
+}
+EXPORT_SYMBOL_GPL(digest_cache_unregister_parser);
--
2.47.0.118.gfd3785337b
More information about the Linux-security-module-archive
mailing list