From: Roberto Sassu roberto.sassu@huawei.com
Introduce digest_cache_populate() to populate the digest cache from a digest list.
It opens the file 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.
The latter function, which at the moment does nothing, will be completed with calls to parsing functions selected from the digest list file name. It expects digest lists file names to be in the format:
[<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.
Failing to populate a digest cache causes it to be marked as invalid and to not be returned by digest_cache_create(). 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/digest_cache/Makefile | 2 +- security/digest_cache/internal.h | 26 ++++++ security/digest_cache/main.c | 18 ++++ security/digest_cache/modsig.c | 66 ++++++++++++++ security/digest_cache/populate.c | 149 +++++++++++++++++++++++++++++++ 6 files changed, 261 insertions(+), 1 deletion(-) create mode 100644 security/digest_cache/modsig.c create mode 100644 security/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/digest_cache/Makefile b/security/digest_cache/Makefile index 7e00c53d8f55..c1452437d02f 100644 --- a/security/digest_cache/Makefile +++ b/security/digest_cache/Makefile @@ -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 diff --git a/security/digest_cache/internal.h b/security/digest_cache/internal.h index ee6487acce1d..0d9c01dd9bc8 100644 --- a/security/digest_cache/internal.h +++ b/security/digest_cache/internal.h @@ -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 */ diff --git a/security/digest_cache/main.c b/security/digest_cache/main.c index fa435b6566fa..8e9ab99081c3 100644 --- a/security/digest_cache/main.c +++ b/security/digest_cache/main.c @@ -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); diff --git a/security/digest_cache/modsig.c b/security/digest_cache/modsig.c new file mode 100644 index 000000000000..3bdda00d8bb2 --- /dev/null +++ b/security/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/digest_cache/populate.c b/security/digest_cache/populate.c new file mode 100644 index 000000000000..415e638f587b --- /dev/null +++ b/security/digest_cache/populate.c @@ -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; +}