diff mbox series

[v5,03/14] digest_cache: Initialize digest caches

Message ID 20240905150543.3766895-4-roberto.sassu@huaweicloud.com (mailing list archive)
State Handled Elsewhere
Headers show
Series integrity: Introduce the Integrity Digest Cache | expand

Commit Message

Roberto Sassu Sept. 5, 2024, 3:05 p.m. UTC
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.

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.

To avoid that the inode the digest cache is created from is evicted or
is different due to a path rename between creation and initialization, take
the path at creation time, and use it at initialization time. Since the
digest cache holds a path reference, the inode cannot be evicted or change.
However, care must be taken to ensure that digest_cache_init() is always
executed after digest_cache_create() or, otherwise, the path reference will
not be released.

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 |  9 ++++
 security/integrity/digest_cache/main.c     | 51 ++++++++++++++++++++++
 2 files changed, 60 insertions(+)
diff mbox series

Patch

diff --git a/security/integrity/digest_cache/internal.h b/security/integrity/digest_cache/internal.h
index e8a13afaf2fc..54e118a2ef79 100644
--- a/security/integrity/digest_cache/internal.h
+++ b/security/integrity/digest_cache/internal.h
@@ -13,11 +13,17 @@ 
 #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
  * @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
  *
  * This structure represents a cache of digests extracted from a digest list.
  */
@@ -25,6 +31,7 @@  struct digest_cache {
 	atomic_t ref_count;
 	char *path_str;
 	unsigned long flags;
+	struct path digest_list_path;
 };
 
 /**
@@ -84,6 +91,8 @@  digest_cache_unref(struct digest_cache *digest_cache)
 struct digest_cache *digest_cache_create(struct dentry *dentry,
 					 struct path *digest_list_path,
 					 char *path_str, char *filename);
+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);
 
diff --git a/security/integrity/digest_cache/main.c b/security/integrity/digest_cache/main.c
index 60030df04a4d..188f1dcc880e 100644
--- a/security/integrity/digest_cache/main.c
+++ b/security/integrity/digest_cache/main.c
@@ -154,6 +154,14 @@  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);
+
+	/* Get the digest list path for initialization. */
+	digest_cache->digest_list_path.dentry = digest_list_path->dentry;
+	digest_cache->digest_list_path.mnt = digest_list_path->mnt;
+	path_get(&digest_cache->digest_list_path);
 	mutex_unlock(&dig_sec->dig_owner_mutex);
 out:
 	if (digest_list_path == &file_path)
@@ -226,6 +234,45 @@  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_cache: Digest cache to initialize
+ *
+ * This function checks if the INIT_STARTED digest cache flag is set. If it is,
+ * it waits until the caller that saw INIT_STARTED unset completes the
+ * initialization.
+ *
+ * Otherwise, it 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.
+ */
+struct digest_cache *digest_cache_init(struct dentry *dentry,
+				       struct digest_cache *digest_cache)
+{
+	/* Wait for digest cache initialization. */
+	if (test_and_set_bit(INIT_STARTED, &digest_cache->flags)) {
+		wait_on_bit(&digest_cache->flags, INIT_IN_PROGRESS,
+			    TASK_UNINTERRUPTIBLE);
+		goto out;
+	}
+
+	path_put(&digest_cache->digest_list_path);
+	/* 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
  * @dentry: Dentry of the inode for which the digest cache will be used
@@ -268,6 +315,10 @@  struct digest_cache *digest_cache_get(struct dentry *dentry)
 
 	mutex_unlock(&dig_sec->dig_user_mutex);
 
+	if (digest_cache)
+		/* This must be always executed, or path ref. is not released.*/
+		digest_cache = digest_cache_init(dentry, digest_cache);
+
 	return digest_cache;
 }
 EXPORT_SYMBOL_GPL(digest_cache_get);