From: Roberto Sassu roberto.sassu@huawei.com
The digest_cache LSM can support other LSMs in their decisions of granting access to file content 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 whether the digest cache itself was created from authentic data.
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.
Reserve space in the file descriptor security blob for the digest cache pointer. Also introduce digest_cache_to_file_sec() to set that pointer before calling kernel_read_file() in digest_cache_populate(), and digest_cache_from_file_sec() to retrieve the pointer back from the file descriptor passed by LSMs with digest_cache_verif_set().
Multiple providers are supported, in the event there are multiple integrity LSMs active. Each provider should also provide an unique verifier ID as an argument to digest_cache_verif_set(), so that verification data can be distinguished.
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, call digest_cache_from_found_t() to convert the returned digest_cache_found_t type to a digest cache pointer, and pass that to digest_cache_verif_get().
Signed-off-by: Roberto Sassu roberto.sassu@huawei.com --- include/linux/digest_cache.h | 17 +++++ security/digest_cache/Makefile | 2 +- security/digest_cache/internal.h | 40 +++++++++++ security/digest_cache/main.c | 4 ++ security/digest_cache/populate.c | 2 + security/digest_cache/verif.c | 116 +++++++++++++++++++++++++++++++ 6 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 security/digest_cache/verif.c
diff --git a/include/linux/digest_cache.h b/include/linux/digest_cache.h index 4872700ac205..9db8128513ca 100644 --- a/include/linux/digest_cache.h +++ b/include/linux/digest_cache.h @@ -44,6 +44,10 @@ void digest_cache_put(struct digest_cache *digest_cache); digest_cache_found_t 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);
#else static inline struct digest_cache *digest_cache_get(struct dentry *dentry) @@ -62,5 +66,18 @@ digest_cache_lookup(struct dentry *dentry, struct digest_cache *digest_cache, return 0UL; }
+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; +} + #endif /* CONFIG_SECURITY_DIGEST_CACHE */ #endif /* _LINUX_DIGEST_CACHE_H */ diff --git a/security/digest_cache/Makefile b/security/digest_cache/Makefile index eca4076497e6..37a473c7bc28 100644 --- a/security/digest_cache/Makefile +++ b/security/digest_cache/Makefile @@ -4,7 +4,7 @@
obj-$(CONFIG_SECURITY_DIGEST_CACHE) += digest_cache.o
-digest_cache-y := main.o secfs.o htable.o populate.o modsig.o +digest_cache-y := main.o secfs.o htable.o populate.o modsig.o verif.o
digest_cache-y += parsers/tlv.o digest_cache-y += parsers/rpm.o diff --git a/security/digest_cache/internal.h b/security/digest_cache/internal.h index 0d9c01dd9bc8..a266925a6cce 100644 --- a/security/digest_cache/internal.h +++ b/security/digest_cache/internal.h @@ -17,6 +17,21 @@ #define INIT_IN_PROGRESS 0 /* Digest cache being initialized. */ #define INVALID 1 /* 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 @@ -71,6 +86,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: Protect concurrent verification data additions * * This structure represents a cache of digests extracted from a digest list. */ @@ -79,6 +96,8 @@ struct digest_cache { atomic_t ref_count; char *path_str; unsigned long flags; + struct list_head verif_data; + spinlock_t verif_data_lock; };
/** @@ -130,6 +149,24 @@ digest_cache_unref(struct digest_cache *digest_cache) return (ref_is_zero) ? digest_cache : NULL; }
+static inline void digest_cache_to_file_sec(const struct file *file, + struct digest_cache *digest_cache) +{ + struct digest_cache **digest_cache_sec; + + digest_cache_sec = file->f_security + digest_cache_blob_sizes.lbs_file; + *digest_cache_sec = digest_cache; +} + +static inline struct digest_cache * +digest_cache_from_file_sec(const struct file *file) +{ + struct digest_cache **digest_cache_sec; + + digest_cache_sec = file->f_security + digest_cache_blob_sizes.lbs_file; + return *digest_cache_sec; +} + /* main.c */ struct digest_cache *digest_cache_create(struct dentry *dentry, struct path *digest_list_path, @@ -153,4 +190,7 @@ int digest_cache_populate(struct digest_cache *digest_cache, /* 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/digest_cache/main.c b/security/digest_cache/main.c index 8e9ab99081c3..d726832e5913 100644 --- a/security/digest_cache/main.c +++ b/security/digest_cache/main.c @@ -49,6 +49,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)); @@ -65,6 +67,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); @@ -329,6 +332,7 @@ EXPORT_SYMBOL_GPL(digest_cache_put);
struct lsm_blob_sizes digest_cache_blob_sizes __ro_after_init = { .lbs_inode = sizeof(struct digest_cache_security), + .lbs_file = sizeof(struct digest_cache *), };
/** diff --git a/security/digest_cache/populate.c b/security/digest_cache/populate.c index 1770c8385017..9c2fc2295310 100644 --- a/security/digest_cache/populate.c +++ b/security/digest_cache/populate.c @@ -123,6 +123,8 @@ int digest_cache_populate(struct digest_cache *digest_cache, return PTR_ERR(file); }
+ digest_cache_to_file_sec(file, digest_cache); + w.data = NULL; w.file = file; INIT_WORK_ONSTACK(&w.work, digest_cache_read_digest_list); diff --git a/security/digest_cache/verif.c b/security/digest_cache/verif.c new file mode 100644 index 000000000000..dd480bdc805a --- /dev/null +++ b/security/digest_cache/verif.c @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2023-2024 Huawei Technologies Duesseldorf GmbH + * + * Author: Roberto Sassu roberto.sassu@huawei.com + * + * 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. + * + * Return: Zero on success, -ENOMEM if out of memory. + */ +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; + + /* + * All allocations must be atomic (non-sleepable) since kprobe does not + * allow otherwise (kprobe is needed for testing). + */ + new_verif = kzalloc(sizeof(*new_verif), GFP_ATOMIC); + if (!new_verif) + return -ENOMEM; + + new_verif->verif_id = kstrdup(verif_id, GFP_ATOMIC); + if (!new_verif->verif_id) { + free_verif(new_verif); + return -ENOMEM; + } + + new_verif->data = kmemdup(data, size, GFP_ATOMIC); + if (!new_verif->data) { + free_verif(new_verif); + return -ENOMEM; + } + + spin_lock(&digest_cache->verif_data_lock); + list_add_tail_rcu(&new_verif->list, &digest_cache->verif_data); + spin_unlock(&digest_cache->verif_data_lock); + return 0; +} +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); + } +}