@@ -15,6 +15,28 @@
struct digest_cache;
+/**
+ * enum digest_cache_event - Events occurring on a digest cache
+ *
+ * This enum lists all the events occurring on a digest cache, to be notified
+ * outside the digest_cache LSM.
+ */
+enum digest_cache_event {
+ DIGEST_CACHE_RESET,
+};
+
+/**
+ * struct digest_cache_event_data - Information on digest cache events
+ * @digest_cache: Digest cache
+ * @inode: Inode for which the digest cache was requested
+ *
+ * This structure holds information about events occurring on a digest cache.
+ */
+struct digest_cache_event_data {
+ struct digest_cache *digest_cache;
+ struct inode *inode;
+};
+
/**
* typedef digest_cache_found_t - Digest cache reference as numeric value
*
@@ -48,6 +70,8 @@ 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);
+int digest_cache_register_notifier(struct notifier_block *nb);
+int digest_cache_unregister_notifier(struct notifier_block *nb);
#else
static inline struct digest_cache *digest_cache_get(struct dentry *dentry)
@@ -79,5 +103,15 @@ static inline void *digest_cache_verif_get(struct digest_cache *digest_cache,
return NULL;
}
+static inline int digest_cache_register_notifier(struct notifier_block *nb)
+{
+ return -EOPNOTSUPP;
+}
+
+static inline int digest_cache_unregister_notifier(struct notifier_block *nb)
+{
+ return -EOPNOTSUPP;
+}
+
#endif /* CONFIG_SECURITY_DIGEST_CACHE */
#endif /* _LINUX_DIGEST_CACHE_H */
@@ -5,7 +5,7 @@
obj-$(CONFIG_SECURITY_DIGEST_CACHE) += digest_cache.o
digest_cache-y := main.o secfs.o htable.o populate.o modsig.o verif.o dir.o \
- reset.o
+ reset.o notifier.o
digest_cache-y += parsers/tlv.o
digest_cache-y += parsers/rpm.o
@@ -21,6 +21,19 @@
#define RESET 4 /* Digest cache to be recreated. */
#define RESET_USER 5 /* Dig_user pointer to be released. */
+/**
+ * struct notify_inode - Structure with inode for which notification is emitted
+ * @list: Linked list
+ * @inode: Inode for which a notification is emitted
+ *
+ * This structure contains an inode for which a notification of a digest cache
+ * event is emitted.
+ */
+struct notify_inode {
+ struct list_head list;
+ struct inode *inode;
+};
+
/**
* struct readdir_callback - Structure to store information for dir iteration
* @ctx: Context structure
@@ -126,6 +139,7 @@ struct htable {
* @path_str: Path of the digest list the digest cache was created from
* @flags: Control flags
* @verif_data: Verification data regarding the digest list
+ * @notify_inodes: List of inodes for which a notification is emitted
* @mutex: Protect digest cache modifications
*
* This structure represents a cache of digests extracted from a digest list.
@@ -137,6 +151,7 @@ struct digest_cache {
char *path_str;
unsigned long flags;
struct list_head verif_data;
+ struct list_head notify_inodes;
struct mutex mutex;
};
@@ -261,4 +276,15 @@ void digest_cache_inode_post_setxattr(struct dentry *dentry, const char *name,
void digest_cache_inode_post_removexattr(struct dentry *dentry,
const char *name);
+/* notifier.c */
+int digest_cache_notify_inode_add(struct digest_cache *digest_cache,
+ struct inode *inode);
+void digest_cache_notify_inodes_free(struct digest_cache *digest_cache);
+void digest_cache_notify(struct digest_cache *digest_cache, struct inode *inode,
+ enum digest_cache_event event);
+void digest_cache_notify_multiple(struct digest_cache *digest_cache,
+ enum digest_cache_event event);
+int digest_cache_register_notifier(struct notifier_block *nb);
+int digest_cache_unregister_notifier(struct notifier_block *nb);
+
#endif /* _DIGEST_CACHE_INTERNAL_H */
@@ -51,6 +51,7 @@ static struct digest_cache *digest_cache_alloc_init(char *path_str,
INIT_LIST_HEAD(&digest_cache->htables);
INIT_LIST_HEAD(&digest_cache->verif_data);
INIT_LIST_HEAD(&digest_cache->dir_entries);
+ INIT_LIST_HEAD(&digest_cache->notify_inodes);
mutex_init(&digest_cache->mutex);
pr_debug("New digest cache %s (ref count: %d)\n",
@@ -70,6 +71,7 @@ static void digest_cache_free(struct digest_cache *digest_cache)
digest_cache_htable_free(digest_cache);
digest_cache_verif_free(digest_cache);
digest_cache_dir_free(digest_cache);
+ digest_cache_notify_inodes_free(digest_cache);
mutex_destroy(&digest_cache->mutex);
pr_debug("Freed digest cache %s\n", digest_cache->path_str);
@@ -398,6 +400,7 @@ struct digest_cache *digest_cache_get(struct dentry *dentry)
struct digest_cache_security *dig_sec;
struct digest_cache *digest_cache = NULL;
struct inode *inode = d_backing_inode(dentry);
+ int ret;
if (!digest_cache_enabled)
return NULL;
@@ -420,6 +423,15 @@ struct digest_cache *digest_cache_get(struct dentry *dentry)
/* Consume extra reference from digest_cache_create(). */
dig_sec->dig_user = digest_cache_new(dentry);
up_read(&default_path_sem);
+
+ if (dig_sec->dig_user) {
+ ret = digest_cache_notify_inode_add(dig_sec->dig_user,
+ inode);
+ if (ret < 0) {
+ digest_cache_put(dig_sec->dig_user);
+ dig_sec->dig_user = NULL;
+ }
+ }
}
if (dig_sec->dig_user)
new file mode 100644
@@ -0,0 +1,135 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2024 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ *
+ * Implement the notifier of the digest_cache LSM.
+ */
+
+#define pr_fmt(fmt) "DIGEST CACHE: "fmt
+
+#include "internal.h"
+
+static BLOCKING_NOTIFIER_HEAD(chain);
+
+static const char * const events_str[] = {
+ [DIGEST_CACHE_RESET] = __stringify(DIGEST_CACHE_RESET),
+};
+
+/**
+ * digest_cache_notify_inode_add - Add inode to digest cache notification list
+ * @digest_cache: Digest cache
+ * @inode: Inode to add
+ *
+ * This function adds an inode to the digest cache notification list, so that
+ * notifications are emitted for that inode.
+ *
+ * Return: Zero on success, -ENOMEM on error.
+ */
+int digest_cache_notify_inode_add(struct digest_cache *digest_cache,
+ struct inode *inode)
+{
+ struct notify_inode *notify_inode;
+
+ notify_inode = kmalloc(sizeof(*notify_inode), GFP_KERNEL);
+ if (!notify_inode)
+ return -ENOMEM;
+
+ notify_inode->inode = inode;
+
+ mutex_lock(&digest_cache->mutex);
+ list_add_tail(¬ify_inode->list, &digest_cache->notify_inodes);
+ pr_debug("Added inode %lu to notification list of digest cache %s\n",
+ inode->i_ino, digest_cache->path_str);
+ mutex_unlock(&digest_cache->mutex);
+ return 0;
+}
+
+/**
+ * digest_cache_notify_inodes_free - Free digest cache notification list
+ * @digest_cache: Digest cache
+ *
+ * This function removes all inodes from the notification list of the passed
+ * digest cache and frees the memory. Does not need locking, since it is called
+ * only at the time the digest cache is freed.
+ */
+void digest_cache_notify_inodes_free(struct digest_cache *digest_cache)
+{
+ struct notify_inode *p, *q;
+
+ list_for_each_entry_safe(p, q, &digest_cache->notify_inodes, list) {
+ list_del(&p->list);
+ pr_debug("Removed inode %lu from notification list of digest cache %s\n",
+ p->inode->i_ino, digest_cache->path_str);
+ kfree(p);
+ }
+}
+
+/**
+ * digest_cache_notify - Emit notification for an inode
+ * @digest_cache: Digest cache
+ * @inode: Inode for which a digest cache is used
+ * @event: Event to notify
+ *
+ * This function emits a notification of a digest cache event for an affected
+ * inode.
+ */
+void digest_cache_notify(struct digest_cache *digest_cache, struct inode *inode,
+ enum digest_cache_event event)
+{
+ struct digest_cache_event_data event_data = {
+ .digest_cache = digest_cache,
+ .inode = inode,
+ };
+
+ pr_debug("Notify event %s for inode %lu using digest cache %s\n",
+ events_str[event], inode->i_ino, digest_cache->path_str);
+
+ blocking_notifier_call_chain(&chain, event, &event_data);
+}
+
+/**
+ * digest_cache_notify_multiple - Emit notification for all inodes in the list
+ * @digest_cache: Digest cache
+ * @event: Event to notify
+ *
+ * This function emits a notification for all inodes in the notification list
+ * of the passed digest cache.
+ */
+void digest_cache_notify_multiple(struct digest_cache *digest_cache,
+ enum digest_cache_event event)
+{
+ struct notify_inode *p;
+
+ list_for_each_entry(p, &digest_cache->notify_inodes, list)
+ digest_cache_notify(digest_cache, p->inode, event);
+}
+
+/**
+ * digest_cache_register_notifier() - Register a digest cache notifier
+ * @nb: Notifier block with the callback
+ *
+ * This function registers a new notifier for events occurring on digest caches.
+ *
+ * Return: Zero on success, -EEXIST on error.
+ */
+int digest_cache_register_notifier(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_register(&chain, nb);
+}
+EXPORT_SYMBOL(digest_cache_register_notifier);
+
+/**
+ * digest_cache_unregister_notifier() - Unregister a digest cache notifier
+ * @nb: Notifier block with the callback
+ *
+ * This function unregisters a previously registered notifier.
+ *
+ * Return: Zero on success, -ENOENT on error.
+ */
+int digest_cache_unregister_notifier(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_unregister(&chain, nb);
+}
+EXPORT_SYMBOL(digest_cache_unregister_notifier);
@@ -10,6 +10,29 @@
#define pr_fmt(fmt) "DIGEST CACHE: "fmt
#include "internal.h"
+/**
+ * digest_cache_notify_dir_entry_reset - Notify dir entry reset
+ * @dir: Directory containing the digest lists
+ *
+ * Emit a notification for the directory containing the digest lists about a
+ * reset occurring on a directory entry.
+ */
+
+static void digest_cache_notify_dir_entry_reset(struct inode *dir)
+{
+ struct digest_cache_security *dir_dig_sec;
+
+ dir_dig_sec = digest_cache_get_security(dir);
+ if (unlikely(!dir_dig_sec))
+ return;
+
+ mutex_lock(&dir_dig_sec->dig_owner_mutex);
+ if (dir_dig_sec->dig_owner)
+ digest_cache_notify_multiple(dir_dig_sec->dig_owner,
+ DIGEST_CACHE_RESET);
+ mutex_unlock(&dir_dig_sec->dig_owner_mutex);
+}
+
/**
* digest_cache_reset_owner - Reset dig_owner
* @inode: Inode of the digest list/directory containing the digest list
@@ -19,6 +42,9 @@
* (if unset), so that digest_cache_get() and digest_cache_create() respectively
* release and clear dig_user and dig_owner in the inode security blob. This
* causes new callers of digest_cache_get() to get a new digest cache.
+ *
+ * After setting RESET, it emits a notification for all inodes using the digest
+ * cache.
*/
static void digest_cache_reset_owner(struct inode *inode, const char *reason)
{
@@ -30,9 +56,12 @@ static void digest_cache_reset_owner(struct inode *inode, const char *reason)
mutex_lock(&dig_sec->dig_owner_mutex);
if (dig_sec->dig_owner &&
- !test_and_set_bit(RESET, &dig_sec->dig_owner->flags))
+ !test_and_set_bit(RESET, &dig_sec->dig_owner->flags)) {
pr_debug("Resetting %s (dig_owner), reason: %s\n",
dig_sec->dig_owner->path_str, reason);
+ digest_cache_notify_multiple(dig_sec->dig_owner,
+ DIGEST_CACHE_RESET);
+ }
mutex_unlock(&dig_sec->dig_owner_mutex);
}
@@ -55,9 +84,12 @@ static void digest_cache_reset_user(struct inode *inode, const char *reason)
mutex_lock(&dig_sec->dig_user_mutex);
if (dig_sec->dig_user &&
- !test_and_set_bit(RESET_USER, &dig_sec->dig_user->flags))
+ !test_and_set_bit(RESET_USER, &dig_sec->dig_user->flags)) {
pr_debug("Resetting %s (dig_user), reason: %s\n",
dig_sec->dig_user->path_str, reason);
+ digest_cache_notify(dig_sec->dig_user, inode,
+ DIGEST_CACHE_RESET);
+ }
mutex_unlock(&dig_sec->dig_user_mutex);
}
@@ -74,11 +106,14 @@ static void digest_cache_reset_user(struct inode *inode, const char *reason)
int digest_cache_path_truncate(const struct path *path)
{
struct inode *inode = d_backing_inode(path->dentry);
+ struct inode *dir = d_backing_inode(path->dentry->d_parent);
if (!S_ISREG(inode->i_mode))
return 0;
digest_cache_reset_owner(inode, "file_truncate");
+ /* Dir digest cache users should know a dir entry changed. */
+ digest_cache_notify_dir_entry_reset(dir);
return 0;
}
@@ -101,6 +136,9 @@ void digest_cache_file_release(struct file *file)
digest_cache_reset_owner(file_inode(file), "file_file_release");
if (file->f_mode & FMODE_CREATED)
digest_cache_reset_owner(dir, "dir_file_release");
+ else
+ /* Dir digest cache users should know a dir entry changed. */
+ digest_cache_notify_dir_entry_reset(dir);
}
/**