[PATCH v5 09/14] digest_cache: Add management of verification data

From: Roberto Sassu
Date: Thu Sep 05 2024 - 11:11:27 EST


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.

Reserve space in the file descriptor security blob for the digest cache
pointer (through IMA). Also introduce digest_cache_to_file_sec() to set
that pointer before reading the digest list, 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 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
uintptr_t value cast to (struct digest_cache *) to
digest_cache_verif_get().

Signed-off-by: Roberto Sassu <roberto.sassu@xxxxxxxxxx>
---
include/linux/digest_cache.h | 17 +++
security/integrity/digest_cache/Makefile | 2 +-
security/integrity/digest_cache/internal.h | 43 ++++++-
security/integrity/digest_cache/main.c | 8 +-
security/integrity/digest_cache/populate.c | 2 +
security/integrity/digest_cache/verif.c | 127 +++++++++++++++++++++
security/integrity/ima/ima_main.c | 5 +-
7 files changed, 200 insertions(+), 4 deletions(-)
create mode 100644 security/integrity/digest_cache/verif.c

diff --git a/include/linux/digest_cache.h b/include/linux/digest_cache.h
index 53a7edc04310..92e17100e9c9 100644
--- a/include/linux/digest_cache.h
+++ b/include/linux/digest_cache.h
@@ -19,6 +19,10 @@ void digest_cache_put(struct digest_cache *digest_cache);
uintptr_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)
@@ -37,5 +41,18 @@ static inline uintptr_t digest_cache_lookup(struct dentry *dentry,
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_INTEGRITY_DIGEST_CACHE */
#endif /* _LINUX_DIGEST_CACHE_H */
diff --git a/security/integrity/digest_cache/Makefile b/security/integrity/digest_cache/Makefile
index 681276a4c756..77dd98a1a07d 100644
--- a/security/integrity/digest_cache/Makefile
+++ b/security/integrity/digest_cache/Makefile
@@ -4,7 +4,7 @@

obj-$(CONFIG_INTEGRITY_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/integrity/digest_cache/internal.h b/security/integrity/digest_cache/internal.h
index f8ec51405bae..9083a87374a5 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
@@ -73,6 +88,8 @@ struct htable {
* @path_str: Path of the digest list the digest cache was created from
* @flags: Control flags
* @digest_list_path: Path structure of the digest list
+ * @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.
*/
@@ -82,6 +99,8 @@ struct digest_cache {
char *path_str;
unsigned long flags;
struct path digest_list_path;
+ struct list_head verif_data;
+ spinlock_t verif_data_lock;
};

/**
@@ -102,6 +121,7 @@ struct digest_cache_security {
};

extern loff_t inode_sec_offset;
+extern loff_t file_sec_offset;
extern char *default_path_str;
extern struct rw_semaphore default_path_sem;

@@ -138,6 +158,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 + file_sec_offset;
+ *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 + file_sec_offset;
+ return *digest_cache_sec;
+}
+
/* main.c */
struct digest_cache *digest_cache_create(struct dentry *dentry,
struct path *digest_list_path,
@@ -145,7 +183,7 @@ struct digest_cache *digest_cache_create(struct dentry *dentry,
struct digest_cache *digest_cache_init(struct dentry *dentry,
struct digest_cache *digest_cache);
int __init digest_cache_do_init(const struct lsm_id *lsm_id,
- loff_t inode_offset);
+ loff_t inode_offset, loff_t file_offset);

/* secfs.c */
int __init digest_cache_secfs_init(struct dentry *dir);
@@ -167,4 +205,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/integrity/digest_cache/main.c b/security/integrity/digest_cache/main.c
index 6878ebe5b779..fda6ac599a2d 100644
--- a/security/integrity/digest_cache/main.c
+++ b/security/integrity/digest_cache/main.c
@@ -17,6 +17,7 @@ static int digest_cache_enabled __ro_after_init;
static struct kmem_cache *digest_cache_cache __read_mostly;

loff_t inode_sec_offset;
+loff_t file_sec_offset;

char *default_path_str = CONFIG_DIGEST_LIST_DEFAULT_PATH;

@@ -51,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));
@@ -67,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);
@@ -421,17 +425,19 @@ static struct security_hook_list digest_cache_hooks[] __ro_after_init = {
* digest_cache_do_init - Initialize the Integrity Digest Cache
* @lsm_id: ID of LSM registering the LSM hooks
* @inode_offset: Offset in the inode security blob
+ * @file_offset: Offset in the file security blob
*
* Initialize the Integrity Digest Cache, by instantiating a cache for the
* digest_cache structure and by registering the LSM hooks as part of the
* calling LSM.
*/
int __init digest_cache_do_init(const struct lsm_id *lsm_id,
- loff_t inode_offset)
+ loff_t inode_offset, loff_t file_offset)
{
init_rwsem(&default_path_sem);

inode_sec_offset = inode_offset;
+ file_sec_offset = file_offset;

digest_cache_cache = kmem_cache_create("digest_cache_cache",
sizeof(struct digest_cache),
diff --git a/security/integrity/digest_cache/populate.c b/security/integrity/digest_cache/populate.c
index 1c68d957bf1d..1ebf3a11f50b 100644
--- a/security/integrity/digest_cache/populate.c
+++ b/security/integrity/digest_cache/populate.c
@@ -115,6 +115,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/integrity/digest_cache/verif.c b/security/integrity/digest_cache/verif.c
new file mode 100644
index 000000000000..de47bd9dc388
--- /dev/null
+++ b/security/integrity/digest_cache/verif.c
@@ -0,0 +1,127 @@
+// 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;
+
+ 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);
+ }
+}
diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
index 7cbd78ca3be5..646d900828e0 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -1208,7 +1208,8 @@ static int __init init_ima_lsm(void)
init_ima_appraise_lsm(&ima_lsmid);
if (IS_ENABLED(CONFIG_INTEGRITY_DIGEST_CACHE))
digest_cache_do_init(&ima_lsmid, ima_blob_sizes.lbs_inode +
- sizeof(struct ima_iint_cache *));
+ sizeof(struct ima_iint_cache *),
+ ima_blob_sizes.lbs_file);
return 0;
}

@@ -1217,6 +1218,8 @@ struct lsm_blob_sizes ima_blob_sizes __ro_after_init = {
#ifdef CONFIG_INTEGRITY_DIGEST_CACHE
+ sizeof(struct digest_cache_security)
#endif
+ ,
+ .lbs_file = sizeof(struct digest_cache *),
};

DEFINE_LSM(ima) = {
--
2.34.1