From: Roberto Sassu <roberto.sassu@xxxxxxxxxx> 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@xxxxxxxxxx> --- security/integrity/digest_cache/internal.h | 9 ++++ security/integrity/digest_cache/main.c | 51 ++++++++++++++++++++++ 2 files changed, 60 insertions(+) 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); -- 2.34.1