From: Roberto Sassu <roberto.sassu@xxxxxxxxxx> The Integrity Digest Cache can support other LSMs in their decisions of granting access to file data and metadata. However, the information alone about whether a digest was found in a digest cache might not be sufficient, because for example those LSMs wouldn't know about the integrity of the digest list digests were extracted from. Introduce digest_cache_verif_set() to let the same LSMs (or a chosen integrity provider) evaluate the digest list being read during the creation of the digest cache, by implementing the kernel_post_read_file LSM hook, and let them attach their verification data to that digest cache. digest_cache_verif_set() receives as argument a file descriptor and calls digest_cache_from_file_sec() to obtain back the digest cache being created from that file descriptor. The digest cache being created was associated to the file descriptor by digest_cache_populate(), before reading the digest list from the kernel, by calling digest_cache_to_file_sec(). Multiple providers are supported, in the event there are multiple integrity LSMs active. Each provider should also provide a unique verifier ID as an argument to digest_cache_verif_set(), so that verification data can be distinguished. Concurrent set are protected by the verif_data_lock spinlock. A caller of digest_cache_get() can retrieve back the verification data by calling digest_cache_verif_get() and passing a digest cache pointer and the desired verifier ID. Since directory digest caches are not populated themselves, LSMs have to do a lookup first to get the digest cache containing the digest, and pass the returned digest cache reference to digest_cache_verif_get(). Signed-off-by: Roberto Sassu <roberto.sassu@xxxxxxxxxx> --- include/linux/digest_cache.h | 17 +++ security/integrity/digest_cache/Makefile | 3 +- security/integrity/digest_cache/internal.h | 22 ++++ security/integrity/digest_cache/main.c | 3 + security/integrity/digest_cache/verif.c | 131 +++++++++++++++++++++ 5 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 security/integrity/digest_cache/verif.c diff --git a/include/linux/digest_cache.h b/include/linux/digest_cache.h index a9d731990b7c..d2483fe588be 100644 --- a/include/linux/digest_cache.h +++ b/include/linux/digest_cache.h @@ -47,6 +47,10 @@ bool digest_cache_opened_fd(struct file *file); struct digest_cache *digest_cache_lookup(struct dentry *dentry, struct digest_cache *digest_cache, u8 *digest, enum hash_algo algo); +int digest_cache_verif_set(struct file *file, const char *verif_id, void *data, + size_t size); +void *digest_cache_verif_get(struct digest_cache *digest_cache, + const char *verif_id); /* Parser API */ int digest_cache_htable_init(struct digest_cache *digest_cache, u64 num_digests, @@ -81,6 +85,19 @@ digest_cache_lookup(struct dentry *dentry, struct digest_cache *digest_cache, return NULL; } +static inline int digest_cache_verif_set(struct file *file, + const char *verif_id, void *data, + size_t size) +{ + return -EOPNOTSUPP; +} + +static inline void *digest_cache_verif_get(struct digest_cache *digest_cache, + const char *verif_id) +{ + return NULL; +} + static inline int digest_cache_htable_init(struct digest_cache *digest_cache, u64 num_digests, enum hash_algo algo) { diff --git a/security/integrity/digest_cache/Makefile b/security/integrity/digest_cache/Makefile index 3b81edea065b..2a0f2500e227 100644 --- a/security/integrity/digest_cache/Makefile +++ b/security/integrity/digest_cache/Makefile @@ -5,6 +5,7 @@ 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 populate.o modsig.o +digest_cache-y := main.o secfs.o htable.o parsers.o populate.o modsig.o \ + verif.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 2171ea8423ff..c64e91b75a47 100644 --- a/security/integrity/digest_cache/internal.h +++ b/security/integrity/digest_cache/internal.h @@ -18,6 +18,21 @@ #define INIT_STARTED 1 /* Digest cache init started. */ #define INVALID 2 /* Digest cache marked as invalid. */ +/** + * struct digest_cache_verif + * @list: Linked list + * @verif_id: Identifier of who verified the digest list + * @data: Opaque data set by the digest list verifier + * + * This structure contains opaque data containing the result of verification + * of the digest list by a verifier. + */ +struct digest_cache_verif { + struct list_head list; + char *verif_id; + void *data; +}; + /** * struct read_work - Structure to schedule reading a digest list * @work: Work structure @@ -72,6 +87,8 @@ struct htable { * @ref_count: Number of references to the digest cache * @path_str: Path of the digest list the digest cache was created from * @flags: Control flags + * @verif_data: Verification data regarding the digest list + * @verif_data_lock: Protects verification data modifications * * This structure represents a cache of digests extracted from a digest list. */ @@ -80,6 +97,8 @@ struct digest_cache { atomic_t ref_count; char *path_str; unsigned long flags; + struct list_head verif_data; + spinlock_t verif_data_lock; }; /** @@ -191,4 +210,7 @@ int digest_cache_populate(struct dentry *dentry, /* modsig.c */ size_t digest_cache_strip_modsig(__u8 *data, size_t data_len); +/* verif.c */ +void digest_cache_verif_free(struct digest_cache *digest_cache); + #endif /* _DIGEST_CACHE_INTERNAL_H */ diff --git a/security/integrity/digest_cache/main.c b/security/integrity/digest_cache/main.c index ad0f34c7ef9b..11a0445592f0 100644 --- a/security/integrity/digest_cache/main.c +++ b/security/integrity/digest_cache/main.c @@ -52,6 +52,8 @@ static struct digest_cache *digest_cache_alloc_init(char *path_str, atomic_set(&digest_cache->ref_count, 1); digest_cache->flags = 0UL; INIT_LIST_HEAD(&digest_cache->htables); + INIT_LIST_HEAD(&digest_cache->verif_data); + spin_lock_init(&digest_cache->verif_data_lock); pr_debug("New digest cache %s (ref count: %d)\n", digest_cache->path_str, atomic_read(&digest_cache->ref_count)); @@ -68,6 +70,7 @@ static struct digest_cache *digest_cache_alloc_init(char *path_str, static void digest_cache_free(struct digest_cache *digest_cache) { digest_cache_htable_free(digest_cache); + digest_cache_verif_free(digest_cache); pr_debug("Freed digest cache %s\n", digest_cache->path_str); kfree(digest_cache->path_str); diff --git a/security/integrity/digest_cache/verif.c b/security/integrity/digest_cache/verif.c new file mode 100644 index 000000000000..03ebf0de764b --- /dev/null +++ b/security/integrity/digest_cache/verif.c @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2023-2024 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu <roberto.sassu@xxxxxxxxxx> + * + * Manage verification data regarding digest lists. + */ + +#define pr_fmt(fmt) "digest_cache: "fmt +#include "internal.h" + +/** + * free_verif - Free a digest_cache_verif structure + * @verif: digest_cache_verif structure + * + * Free the space allocated for a digest_cache_verif structure. + */ +static void free_verif(struct digest_cache_verif *verif) +{ + kfree(verif->data); + kfree(verif->verif_id); + kfree(verif); +} + +/** + * digest_cache_verif_set - Set digest cache verification data + * @file: File descriptor of the digest list being read to populate digest cache + * @verif_id: Verifier ID + * @data: Verification data (opaque) + * @size: Size of @data + * + * This function lets a verifier supply verification data about a digest list + * being read to populate the digest cache. Verifier ID must be unique. + * + * Return: Zero on success, a POSIX error code otherwise. + */ +int digest_cache_verif_set(struct file *file, const char *verif_id, void *data, + size_t size) +{ + struct digest_cache *digest_cache = digest_cache_from_file_sec(file); + struct digest_cache_verif *new_verif, *verif; + /* All allocations done by kprobe must be atomic (non-sleepable). */ + gfp_t flags = !strncmp(verif_id, "kprobe", 6) ? GFP_ATOMIC : GFP_KERNEL; + int ret = 0; + + /* + * Zero the data, so that we can always call free_verif() to free a + * partially filled structure (if a pointer is NULL, will not be freed). + */ + new_verif = kzalloc(sizeof(*new_verif), flags); + if (!new_verif) + return -ENOMEM; + + new_verif->verif_id = kstrdup(verif_id, flags); + if (!new_verif->verif_id) { + free_verif(new_verif); + return -ENOMEM; + } + + new_verif->data = kmemdup(data, size, flags); + if (!new_verif->data) { + free_verif(new_verif); + return -ENOMEM; + } + + spin_lock(&digest_cache->verif_data_lock); + list_for_each_entry(verif, &digest_cache->verif_data, list) { + if (!strcmp(verif->verif_id, verif_id)) { + ret = -EEXIST; + goto out; + } + } + + list_add_tail_rcu(&new_verif->list, &digest_cache->verif_data); +out: + spin_unlock(&digest_cache->verif_data_lock); + + if (ret < 0) + free_verif(new_verif); + + return ret; +} +EXPORT_SYMBOL_GPL(digest_cache_verif_set); + +/** + * digest_cache_verif_get - Get digest cache verification data + * @digest_cache: Digest cache + * @verif_id: Verifier ID + * + * This function returns the verification data previously set by a verifier + * with digest_cache_verif_set(). + * + * Return: Verification data if found, NULL otherwise. + */ +void *digest_cache_verif_get(struct digest_cache *digest_cache, + const char *verif_id) +{ + struct digest_cache_verif *verif; + void *verif_data = NULL; + + rcu_read_lock(); + list_for_each_entry_rcu(verif, &digest_cache->verif_data, list) { + if (!strcmp(verif->verif_id, verif_id)) { + verif_data = verif->data; + break; + } + } + rcu_read_unlock(); + + return verif_data; +} +EXPORT_SYMBOL_GPL(digest_cache_verif_get); + +/** + * digest_cache_verif_free - Free all digest_cache_verif structures + * @digest_cache: Digest cache + * + * This function frees the space allocated for all digest_cache_verif + * structures in the digest cache. + */ +void digest_cache_verif_free(struct digest_cache *digest_cache) +{ + struct digest_cache_verif *p, *q; + + /* No need to lock, called when nobody else has a digest cache ref. */ + list_for_each_entry_safe(p, q, &digest_cache->verif_data, list) { + list_del(&p->list); + free_verif(p); + } +} -- 2.47.0.118.gfd3785337b