From: Roberto Sassu <roberto.sassu@huawei.com>
Introduce digest_cache_init() to initialize created digest caches. Since
initialization happens after releasing both the dig_owner_mutex and
dig_user_mutex locks (to avoid a lock inversion with VFS locks), any caller
of digest_cache_get() can potentially be in charge of initializing them,
provided that it got the digest list path from digest_cache_new().
Introduce the INIT_STARTED flag, to atomically determine whether the digest
cache is being initialized and eventually do it if the flag is not yet set.
Introduce the INIT_IN_PROGRESS flag, for the other callers to wait until
the caller in charge of the initialization finishes initializing the digest
cache. Set INIT_IN_PROGRESS in digest_cache_create() and clear it in
digest_cache_init(). Finally, call clear_and_wake_up_bit() to wake up the
other callers.
Finally, introduce the INVALID flag, to let the callers which didn't
initialize the digest cache know that an error occurred during
initialization and, consequently, prevent them from using that digest
cache.
Signed-off-by: Roberto Sassu <roberto.sassu@huawei.com>
---
security/integrity/digest_cache/internal.h | 8 ++++
security/integrity/digest_cache/main.c | 48 ++++++++++++++++++++++
2 files changed, 56 insertions(+)
diff --git a/security/integrity/digest_cache/internal.h b/security/integrity/digest_cache/internal.h
index fa76ab2672ea..29bf98a974f3 100644
--- a/security/integrity/digest_cache/internal.h
+++ b/security/integrity/digest_cache/internal.h
@@ -13,6 +13,11 @@
#include <linux/lsm_hooks.h>
#include <linux/digest_cache.h>
+/* Digest cache bits in flags. */
+#define INIT_IN_PROGRESS 0 /* Digest cache being initialized. */
+#define INIT_STARTED 1 /* Digest cache init started. */
+#define INVALID 2 /* Digest cache marked as invalid. */
+
/**
* struct digest_cache - Digest cache
* @ref_count: Number of references to the digest cache
@@ -110,6 +115,9 @@ struct digest_cache *digest_cache_create(struct dentry *dentry,
struct path *default_path,
struct path *digest_list_path,
char *path_str, char *filename);
+struct digest_cache *digest_cache_init(struct dentry *dentry,
+ struct path *digest_list_path,
+ struct digest_cache *digest_cache);
int __init digest_cache_do_init(const struct lsm_id *lsm_id,
loff_t inode_offset, loff_t file_offset);
diff --git a/security/integrity/digest_cache/main.c b/security/integrity/digest_cache/main.c
index c644cdc2ebd7..6e94cff2b0dc 100644
--- a/security/integrity/digest_cache/main.c
+++ b/security/integrity/digest_cache/main.c
@@ -156,6 +156,9 @@ struct digest_cache *digest_cache_create(struct dentry *dentry,
/* Increment ref. count for reference returned to the caller. */
digest_cache = digest_cache_ref(dig_sec->dig_owner);
+
+ /* Make other digest cache requestors wait until creation complete. */
+ set_bit(INIT_IN_PROGRESS, &digest_cache->flags);
out:
mutex_unlock(&dig_sec->dig_owner_mutex);
return digest_cache;
@@ -238,6 +241,47 @@ static struct digest_cache *digest_cache_new(struct dentry *dentry,
return digest_cache;
}
+/**
+ * digest_cache_init - Initialize a digest cache
+ * @dentry: Dentry of the inode for which the digest cache will be used
+ * @digest_list_path: Path structure of the digest list
+ * @digest_cache: Digest cache to initialize
+ *
+ * This function checks if the INIT_STARTED digest cache flag is set. If it is,
+ * or the caller didn't provide the digest list path, it waits until the caller
+ * that saw INIT_STARTED unset and had the path completes the initialization.
+ *
+ * The latter sets INIT_STARTED (atomically), performs the initialization,
+ * clears the INIT_IN_PROGRESS digest cache flag, and wakes up the other
+ * callers.
+ *
+ * Return: A valid and initialized digest cache on success, NULL otherwise.
+ */
+struct digest_cache *digest_cache_init(struct dentry *dentry,
+ struct path *digest_list_path,
+ struct digest_cache *digest_cache)
+{
+ /* Wait for digest cache initialization. */
+ if (!digest_list_path->dentry ||
+ test_and_set_bit(INIT_STARTED, &digest_cache->flags)) {
+ wait_on_bit(&digest_cache->flags, INIT_IN_PROGRESS,
+ TASK_UNINTERRUPTIBLE);
+ goto out;
+ }
+
+ /* Notify initialization complete. */
+ clear_and_wake_up_bit(INIT_IN_PROGRESS, &digest_cache->flags);
+out:
+ 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;
+ }
+
+ return digest_cache;
+}
+
/**
* digest_cache_get - Get a digest cache for a given inode
* @file: File descriptor of the inode for which the digest cache will be used
@@ -287,6 +331,10 @@ struct digest_cache *digest_cache_get(struct file *file)
mutex_unlock(&dig_sec->dig_user_mutex);
+ if (digest_cache)
+ digest_cache = digest_cache_init(dentry, &digest_list_path,
+ digest_cache);
+
if (digest_list_path.dentry)
path_put(&digest_list_path);
--
2.47.0.118.gfd3785337b