From: Roberto Sassu <roberto.sassu@xxxxxxxxxx> 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@xxxxxxxxxx> --- 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@xxxxxxxxxx> + * + * 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@xxxxxxxxxx> + * + * 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