From: Roberto Sassu <roberto.sassu@huawei.com>
Introduce digest_cache_populate() to populate the digest cache from a
digest list. Call it from digest_cache_init() if the inode is a regular
file.
It opens the file, marks it for internal use with
digest_cache_to_file_sec(), and then schedules a work to read the content
(with new file type READING_DIGEST_LIST). Scheduling a work solves the
problem of kernel_read_file() returning -EINTR.
Once the work is done, this function calls digest_cache_strip_modsig() to
strip a module-style appended signature, if present, and finally calls
digest_cache_parse_digest_list() to parse the data.
Failing to populate a digest cache causes it to be marked as invalid and to
not be returned by digest_cache_init(). Dig_owner however is kept, to
avoid an excessive number of retries, which would probably not succeed
either.
Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
include/linux/kernel_read_file.h | 1 +
security/integrity/digest_cache/Makefile | 2 +-
security/integrity/digest_cache/internal.h | 25 ++++++
security/integrity/digest_cache/main.c | 16 ++++
security/integrity/digest_cache/modsig.c | 66 +++++++++++++++
security/integrity/digest_cache/populate.c | 97 ++++++++++++++++++++++
6 files changed, 206 insertions(+), 1 deletion(-)
create mode 100644 security/integrity/digest_cache/modsig.c
create mode 100644 security/integrity/digest_cache/populate.c
diff --git a/include/linux/kernel_read_file.h b/include/linux/kernel_read_file.h
index 90451e2e12bd..85f602e49e2f 100644
--- a/include/linux/kernel_read_file.h
+++ b/include/linux/kernel_read_file.h
@@ -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,
diff --git a/security/integrity/digest_cache/Makefile b/security/integrity/digest_cache/Makefile
index 3b42b20d1bc0..3b81edea065b 100644
--- a/security/integrity/digest_cache/Makefile
+++ b/security/integrity/digest_cache/Makefile
@@ -5,6 +5,6 @@
obj-$(CONFIG_INTEGRITY_DIGEST_CACHE) += digest_cache.o
obj-$(CONFIG_DIGEST_CACHE_TLV_PARSER) += parsers/tlv.o
-digest_cache-y := main.o secfs.o htable.o parsers.o
+digest_cache-y := main.o secfs.o htable.o parsers.o populate.o modsig.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 e178549f9ff9..2171ea8423ff 100644
--- a/security/integrity/digest_cache/internal.h
+++ b/security/integrity/digest_cache/internal.h
@@ -18,6 +18,23 @@
#define INIT_STARTED 1 /* Digest cache init started. */
#define INVALID 2 /* 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
* @hnext: Pointer to the next element in the collision list
@@ -166,4 +183,12 @@ int digest_cache_parse_digest_list(struct dentry *dentry,
struct digest_cache *digest_cache,
char *path_str, void *data, size_t data_len);
+/* populate.c */
+int digest_cache_populate(struct dentry *dentry,
+ struct digest_cache *digest_cache,
+ struct path *digest_list_path);
+
+/* modsig.c */
+size_t digest_cache_strip_modsig(__u8 *data, size_t data_len);
+
#endif /* _DIGEST_CACHE_INTERNAL_H */
diff --git a/security/integrity/digest_cache/main.c b/security/integrity/digest_cache/main.c
index ebc5dc09a62b..ad0f34c7ef9b 100644
--- a/security/integrity/digest_cache/main.c
+++ b/security/integrity/digest_cache/main.c
@@ -267,6 +267,9 @@ struct digest_cache *digest_cache_init(struct dentry *dentry,
struct path *digest_list_path,
struct digest_cache *digest_cache)
{
+ struct inode *inode;
+ int ret;
+
/* Wait for digest cache initialization. */
if (!digest_list_path->dentry ||
test_and_set_bit(INIT_STARTED, &digest_cache->flags)) {
@@ -275,6 +278,19 @@ struct digest_cache *digest_cache_init(struct dentry *dentry,
goto out;
}
+ inode = d_backing_inode(digest_list_path->dentry);
+
+ if (S_ISREG(inode->i_mode)) {
+ ret = digest_cache_populate(dentry, digest_cache,
+ digest_list_path);
+ 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);
+ }
+ }
+
/* Notify initialization complete. */
clear_and_wake_up_bit(INIT_IN_PROGRESS, &digest_cache->flags);
out:
diff --git a/security/integrity/digest_cache/modsig.c b/security/integrity/digest_cache/modsig.c
new file mode 100644
index 000000000000..fa512c43a556
--- /dev/null
+++ b/security/integrity/digest_cache/modsig.c
@@ -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;
+}
diff --git a/security/integrity/digest_cache/populate.c b/security/integrity/digest_cache/populate.c
new file mode 100644
index 000000000000..54f7f95f5794
--- /dev/null
+++ b/security/integrity/digest_cache/populate.c
@@ -0,0 +1,97 @@
+// 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/vmalloc.h>
+#include <linux/kernel_read_file.h>
+
+#include "internal.h"
+
+/**
+ * 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
+ * @dentry: Dentry of the inode for which the digest cache will be used
+ * @digest_cache: Digest cache
+ * @digest_list_path: Path structure of the digest list
+ *
+ * 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 dentry *dentry,
+ struct digest_cache *digest_cache,
+ struct path *digest_list_path)
+{
+ struct file *file;
+ void *data;
+ size_t data_len;
+ struct read_work w;
+ int ret;
+
+ file = kernel_file_open(digest_list_path, O_RDONLY, &init_cred);
+ if (IS_ERR(file)) {
+ pr_debug("Unable to open digest list %s, ret: %ld\n",
+ digest_cache->path_str, PTR_ERR(file));
+ return PTR_ERR(file);
+ }
+
+ /* Mark the file descriptor as ours. */
+ digest_cache_to_file_sec(file, digest_cache);
+
+ 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, ret: %d\n",
+ digest_cache->path_str, 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(dentry, digest_cache,
+ digest_cache->path_str, data,
+ data_len);
+ if (ret < 0)
+ pr_debug("Error parsing digest list %s, ret: %d\n",
+ digest_cache->path_str, ret);
+
+ vfree(data);
+ return ret;
+}
--
2.47.0.118.gfd3785337b
© 2016 - 2024 Red Hat, Inc.