fs-verity: use mempool for hash requests
When initializing an fs-verity hash algorithm, also initialize a mempool that contains a single preallocated hash request object. Then replace the direct calls to ahash_request_alloc() and ahash_request_free() with allocating and freeing from this mempool. This eliminates the possibility of the allocation failing, which is desirable for the I/O path. This doesn't cause deadlocks because there's no case where multiple hash requests are needed at a time to make forward progress. Link: https://lore.kernel.org/r/20191231175545.20709-1-ebiggers@kernel.org Reviewed-by: Theodore Ts'o <tytso@mit.edu> Signed-off-by: Eric Biggers <ebiggers@google.com>
This commit is contained in:
parent
dcd848f102
commit
62810e0d59
5 changed files with 97 additions and 46 deletions
|
@ -165,9 +165,11 @@ static int build_merkle_tree(struct file *filp,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* This allocation never fails, since it's mempool-backed. */
|
||||||
|
req = fsverity_alloc_hash_request(params->hash_alg, GFP_KERNEL);
|
||||||
|
|
||||||
pending_hashes = kmalloc(params->block_size, GFP_KERNEL);
|
pending_hashes = kmalloc(params->block_size, GFP_KERNEL);
|
||||||
req = ahash_request_alloc(params->hash_alg->tfm, GFP_KERNEL);
|
if (!pending_hashes)
|
||||||
if (!pending_hashes || !req)
|
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -189,7 +191,7 @@ static int build_merkle_tree(struct file *filp,
|
||||||
err = 0;
|
err = 0;
|
||||||
out:
|
out:
|
||||||
kfree(pending_hashes);
|
kfree(pending_hashes);
|
||||||
ahash_request_free(req);
|
fsverity_free_hash_request(params->hash_alg, req);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
#include <crypto/sha.h>
|
#include <crypto/sha.h>
|
||||||
#include <linux/fsverity.h>
|
#include <linux/fsverity.h>
|
||||||
|
#include <linux/mempool.h>
|
||||||
|
|
||||||
struct ahash_request;
|
struct ahash_request;
|
||||||
|
|
||||||
|
@ -37,11 +38,12 @@ struct fsverity_hash_alg {
|
||||||
const char *name; /* crypto API name, e.g. sha256 */
|
const char *name; /* crypto API name, e.g. sha256 */
|
||||||
unsigned int digest_size; /* digest size in bytes, e.g. 32 for SHA-256 */
|
unsigned int digest_size; /* digest size in bytes, e.g. 32 for SHA-256 */
|
||||||
unsigned int block_size; /* block size in bytes, e.g. 64 for SHA-256 */
|
unsigned int block_size; /* block size in bytes, e.g. 64 for SHA-256 */
|
||||||
|
mempool_t req_pool; /* mempool with a preallocated hash request */
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Merkle tree parameters: hash algorithm, initial hash state, and topology */
|
/* Merkle tree parameters: hash algorithm, initial hash state, and topology */
|
||||||
struct merkle_tree_params {
|
struct merkle_tree_params {
|
||||||
const struct fsverity_hash_alg *hash_alg; /* the hash algorithm */
|
struct fsverity_hash_alg *hash_alg; /* the hash algorithm */
|
||||||
const u8 *hashstate; /* initial hash state or NULL */
|
const u8 *hashstate; /* initial hash state or NULL */
|
||||||
unsigned int digest_size; /* same as hash_alg->digest_size */
|
unsigned int digest_size; /* same as hash_alg->digest_size */
|
||||||
unsigned int block_size; /* size of data and tree blocks */
|
unsigned int block_size; /* size of data and tree blocks */
|
||||||
|
@ -115,14 +117,18 @@ struct fsverity_signed_digest {
|
||||||
|
|
||||||
extern struct fsverity_hash_alg fsverity_hash_algs[];
|
extern struct fsverity_hash_alg fsverity_hash_algs[];
|
||||||
|
|
||||||
const struct fsverity_hash_alg *fsverity_get_hash_alg(const struct inode *inode,
|
struct fsverity_hash_alg *fsverity_get_hash_alg(const struct inode *inode,
|
||||||
unsigned int num);
|
unsigned int num);
|
||||||
const u8 *fsverity_prepare_hash_state(const struct fsverity_hash_alg *alg,
|
struct ahash_request *fsverity_alloc_hash_request(struct fsverity_hash_alg *alg,
|
||||||
|
gfp_t gfp_flags);
|
||||||
|
void fsverity_free_hash_request(struct fsverity_hash_alg *alg,
|
||||||
|
struct ahash_request *req);
|
||||||
|
const u8 *fsverity_prepare_hash_state(struct fsverity_hash_alg *alg,
|
||||||
const u8 *salt, size_t salt_size);
|
const u8 *salt, size_t salt_size);
|
||||||
int fsverity_hash_page(const struct merkle_tree_params *params,
|
int fsverity_hash_page(const struct merkle_tree_params *params,
|
||||||
const struct inode *inode,
|
const struct inode *inode,
|
||||||
struct ahash_request *req, struct page *page, u8 *out);
|
struct ahash_request *req, struct page *page, u8 *out);
|
||||||
int fsverity_hash_buffer(const struct fsverity_hash_alg *alg,
|
int fsverity_hash_buffer(struct fsverity_hash_alg *alg,
|
||||||
const void *data, size_t size, u8 *out);
|
const void *data, size_t size, u8 *out);
|
||||||
void __init fsverity_check_hash_algs(void);
|
void __init fsverity_check_hash_algs(void);
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,8 @@ struct fsverity_hash_alg fsverity_hash_algs[] = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static DEFINE_MUTEX(fsverity_hash_alg_init_mutex);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* fsverity_get_hash_alg() - validate and prepare a hash algorithm
|
* fsverity_get_hash_alg() - validate and prepare a hash algorithm
|
||||||
* @inode: optional inode for logging purposes
|
* @inode: optional inode for logging purposes
|
||||||
|
@ -36,8 +38,8 @@ struct fsverity_hash_alg fsverity_hash_algs[] = {
|
||||||
*
|
*
|
||||||
* Return: pointer to the hash alg on success, else an ERR_PTR()
|
* Return: pointer to the hash alg on success, else an ERR_PTR()
|
||||||
*/
|
*/
|
||||||
const struct fsverity_hash_alg *fsverity_get_hash_alg(const struct inode *inode,
|
struct fsverity_hash_alg *fsverity_get_hash_alg(const struct inode *inode,
|
||||||
unsigned int num)
|
unsigned int num)
|
||||||
{
|
{
|
||||||
struct fsverity_hash_alg *alg;
|
struct fsverity_hash_alg *alg;
|
||||||
struct crypto_ahash *tfm;
|
struct crypto_ahash *tfm;
|
||||||
|
@ -50,10 +52,15 @@ const struct fsverity_hash_alg *fsverity_get_hash_alg(const struct inode *inode,
|
||||||
}
|
}
|
||||||
alg = &fsverity_hash_algs[num];
|
alg = &fsverity_hash_algs[num];
|
||||||
|
|
||||||
/* pairs with cmpxchg() below */
|
/* pairs with smp_store_release() below */
|
||||||
tfm = READ_ONCE(alg->tfm);
|
if (likely(smp_load_acquire(&alg->tfm) != NULL))
|
||||||
if (likely(tfm != NULL))
|
|
||||||
return alg;
|
return alg;
|
||||||
|
|
||||||
|
mutex_lock(&fsverity_hash_alg_init_mutex);
|
||||||
|
|
||||||
|
if (alg->tfm != NULL)
|
||||||
|
goto out_unlock;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Using the shash API would make things a bit simpler, but the ahash
|
* Using the shash API would make things a bit simpler, but the ahash
|
||||||
* API is preferable as it allows the use of crypto accelerators.
|
* API is preferable as it allows the use of crypto accelerators.
|
||||||
|
@ -64,12 +71,14 @@ const struct fsverity_hash_alg *fsverity_get_hash_alg(const struct inode *inode,
|
||||||
fsverity_warn(inode,
|
fsverity_warn(inode,
|
||||||
"Missing crypto API support for hash algorithm \"%s\"",
|
"Missing crypto API support for hash algorithm \"%s\"",
|
||||||
alg->name);
|
alg->name);
|
||||||
return ERR_PTR(-ENOPKG);
|
alg = ERR_PTR(-ENOPKG);
|
||||||
|
goto out_unlock;
|
||||||
}
|
}
|
||||||
fsverity_err(inode,
|
fsverity_err(inode,
|
||||||
"Error allocating hash algorithm \"%s\": %ld",
|
"Error allocating hash algorithm \"%s\": %ld",
|
||||||
alg->name, PTR_ERR(tfm));
|
alg->name, PTR_ERR(tfm));
|
||||||
return ERR_CAST(tfm);
|
alg = ERR_CAST(tfm);
|
||||||
|
goto out_unlock;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = -EINVAL;
|
err = -EINVAL;
|
||||||
|
@ -78,18 +87,61 @@ const struct fsverity_hash_alg *fsverity_get_hash_alg(const struct inode *inode,
|
||||||
if (WARN_ON(alg->block_size != crypto_ahash_blocksize(tfm)))
|
if (WARN_ON(alg->block_size != crypto_ahash_blocksize(tfm)))
|
||||||
goto err_free_tfm;
|
goto err_free_tfm;
|
||||||
|
|
||||||
|
err = mempool_init_kmalloc_pool(&alg->req_pool, 1,
|
||||||
|
sizeof(struct ahash_request) +
|
||||||
|
crypto_ahash_reqsize(tfm));
|
||||||
|
if (err)
|
||||||
|
goto err_free_tfm;
|
||||||
|
|
||||||
pr_info("%s using implementation \"%s\"\n",
|
pr_info("%s using implementation \"%s\"\n",
|
||||||
alg->name, crypto_ahash_driver_name(tfm));
|
alg->name, crypto_ahash_driver_name(tfm));
|
||||||
|
|
||||||
/* pairs with READ_ONCE() above */
|
/* pairs with smp_load_acquire() above */
|
||||||
if (cmpxchg(&alg->tfm, NULL, tfm) != NULL)
|
smp_store_release(&alg->tfm, tfm);
|
||||||
crypto_free_ahash(tfm);
|
goto out_unlock;
|
||||||
|
|
||||||
return alg;
|
|
||||||
|
|
||||||
err_free_tfm:
|
err_free_tfm:
|
||||||
crypto_free_ahash(tfm);
|
crypto_free_ahash(tfm);
|
||||||
return ERR_PTR(err);
|
alg = ERR_PTR(err);
|
||||||
|
out_unlock:
|
||||||
|
mutex_unlock(&fsverity_hash_alg_init_mutex);
|
||||||
|
return alg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fsverity_alloc_hash_request() - allocate a hash request object
|
||||||
|
* @alg: the hash algorithm for which to allocate the request
|
||||||
|
* @gfp_flags: memory allocation flags
|
||||||
|
*
|
||||||
|
* This is mempool-backed, so this never fails if __GFP_DIRECT_RECLAIM is set in
|
||||||
|
* @gfp_flags. However, in that case this might need to wait for all
|
||||||
|
* previously-allocated requests to be freed. So to avoid deadlocks, callers
|
||||||
|
* must never need multiple requests at a time to make forward progress.
|
||||||
|
*
|
||||||
|
* Return: the request object on success; NULL on failure (but see above)
|
||||||
|
*/
|
||||||
|
struct ahash_request *fsverity_alloc_hash_request(struct fsverity_hash_alg *alg,
|
||||||
|
gfp_t gfp_flags)
|
||||||
|
{
|
||||||
|
struct ahash_request *req = mempool_alloc(&alg->req_pool, gfp_flags);
|
||||||
|
|
||||||
|
if (req)
|
||||||
|
ahash_request_set_tfm(req, alg->tfm);
|
||||||
|
return req;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fsverity_free_hash_request() - free a hash request object
|
||||||
|
* @alg: the hash algorithm
|
||||||
|
* @req: the hash request object to free
|
||||||
|
*/
|
||||||
|
void fsverity_free_hash_request(struct fsverity_hash_alg *alg,
|
||||||
|
struct ahash_request *req)
|
||||||
|
{
|
||||||
|
if (req) {
|
||||||
|
ahash_request_zero(req);
|
||||||
|
mempool_free(req, &alg->req_pool);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -101,7 +153,7 @@ const struct fsverity_hash_alg *fsverity_get_hash_alg(const struct inode *inode,
|
||||||
* Return: NULL if the salt is empty, otherwise the kmalloc()'ed precomputed
|
* Return: NULL if the salt is empty, otherwise the kmalloc()'ed precomputed
|
||||||
* initial hash state on success or an ERR_PTR() on failure.
|
* initial hash state on success or an ERR_PTR() on failure.
|
||||||
*/
|
*/
|
||||||
const u8 *fsverity_prepare_hash_state(const struct fsverity_hash_alg *alg,
|
const u8 *fsverity_prepare_hash_state(struct fsverity_hash_alg *alg,
|
||||||
const u8 *salt, size_t salt_size)
|
const u8 *salt, size_t salt_size)
|
||||||
{
|
{
|
||||||
u8 *hashstate = NULL;
|
u8 *hashstate = NULL;
|
||||||
|
@ -119,11 +171,8 @@ const u8 *fsverity_prepare_hash_state(const struct fsverity_hash_alg *alg,
|
||||||
if (!hashstate)
|
if (!hashstate)
|
||||||
return ERR_PTR(-ENOMEM);
|
return ERR_PTR(-ENOMEM);
|
||||||
|
|
||||||
req = ahash_request_alloc(alg->tfm, GFP_KERNEL);
|
/* This allocation never fails, since it's mempool-backed. */
|
||||||
if (!req) {
|
req = fsverity_alloc_hash_request(alg, GFP_KERNEL);
|
||||||
err = -ENOMEM;
|
|
||||||
goto err_free;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Zero-pad the salt to the next multiple of the input size of the hash
|
* Zero-pad the salt to the next multiple of the input size of the hash
|
||||||
|
@ -158,7 +207,7 @@ const u8 *fsverity_prepare_hash_state(const struct fsverity_hash_alg *alg,
|
||||||
if (err)
|
if (err)
|
||||||
goto err_free;
|
goto err_free;
|
||||||
out:
|
out:
|
||||||
ahash_request_free(req);
|
fsverity_free_hash_request(alg, req);
|
||||||
kfree(padded_salt);
|
kfree(padded_salt);
|
||||||
return hashstate;
|
return hashstate;
|
||||||
|
|
||||||
|
@ -229,7 +278,7 @@ int fsverity_hash_page(const struct merkle_tree_params *params,
|
||||||
*
|
*
|
||||||
* Return: 0 on success, -errno on failure
|
* Return: 0 on success, -errno on failure
|
||||||
*/
|
*/
|
||||||
int fsverity_hash_buffer(const struct fsverity_hash_alg *alg,
|
int fsverity_hash_buffer(struct fsverity_hash_alg *alg,
|
||||||
const void *data, size_t size, u8 *out)
|
const void *data, size_t size, u8 *out)
|
||||||
{
|
{
|
||||||
struct ahash_request *req;
|
struct ahash_request *req;
|
||||||
|
@ -237,9 +286,8 @@ int fsverity_hash_buffer(const struct fsverity_hash_alg *alg,
|
||||||
DECLARE_CRYPTO_WAIT(wait);
|
DECLARE_CRYPTO_WAIT(wait);
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
req = ahash_request_alloc(alg->tfm, GFP_KERNEL);
|
/* This allocation never fails, since it's mempool-backed. */
|
||||||
if (!req)
|
req = fsverity_alloc_hash_request(alg, GFP_KERNEL);
|
||||||
return -ENOMEM;
|
|
||||||
|
|
||||||
sg_init_one(&sg, data, size);
|
sg_init_one(&sg, data, size);
|
||||||
ahash_request_set_callback(req, CRYPTO_TFM_REQ_MAY_SLEEP |
|
ahash_request_set_callback(req, CRYPTO_TFM_REQ_MAY_SLEEP |
|
||||||
|
@ -249,7 +297,7 @@ int fsverity_hash_buffer(const struct fsverity_hash_alg *alg,
|
||||||
|
|
||||||
err = crypto_wait_req(crypto_ahash_digest(req), &wait);
|
err = crypto_wait_req(crypto_ahash_digest(req), &wait);
|
||||||
|
|
||||||
ahash_request_free(req);
|
fsverity_free_hash_request(alg, req);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ int fsverity_init_merkle_tree_params(struct merkle_tree_params *params,
|
||||||
unsigned int log_blocksize,
|
unsigned int log_blocksize,
|
||||||
const u8 *salt, size_t salt_size)
|
const u8 *salt, size_t salt_size)
|
||||||
{
|
{
|
||||||
const struct fsverity_hash_alg *hash_alg;
|
struct fsverity_hash_alg *hash_alg;
|
||||||
int err;
|
int err;
|
||||||
u64 blocks;
|
u64 blocks;
|
||||||
u64 offset;
|
u64 offset;
|
||||||
|
@ -127,7 +127,7 @@ int fsverity_init_merkle_tree_params(struct merkle_tree_params *params,
|
||||||
* Compute the file measurement by hashing the fsverity_descriptor excluding the
|
* Compute the file measurement by hashing the fsverity_descriptor excluding the
|
||||||
* signature and with the sig_size field set to 0.
|
* signature and with the sig_size field set to 0.
|
||||||
*/
|
*/
|
||||||
static int compute_file_measurement(const struct fsverity_hash_alg *hash_alg,
|
static int compute_file_measurement(struct fsverity_hash_alg *hash_alg,
|
||||||
struct fsverity_descriptor *desc,
|
struct fsverity_descriptor *desc,
|
||||||
u8 *measurement)
|
u8 *measurement)
|
||||||
{
|
{
|
||||||
|
|
|
@ -192,13 +192,12 @@ bool fsverity_verify_page(struct page *page)
|
||||||
struct ahash_request *req;
|
struct ahash_request *req;
|
||||||
bool valid;
|
bool valid;
|
||||||
|
|
||||||
req = ahash_request_alloc(vi->tree_params.hash_alg->tfm, GFP_NOFS);
|
/* This allocation never fails, since it's mempool-backed. */
|
||||||
if (unlikely(!req))
|
req = fsverity_alloc_hash_request(vi->tree_params.hash_alg, GFP_NOFS);
|
||||||
return false;
|
|
||||||
|
|
||||||
valid = verify_page(inode, vi, req, page, 0);
|
valid = verify_page(inode, vi, req, page, 0);
|
||||||
|
|
||||||
ahash_request_free(req);
|
fsverity_free_hash_request(vi->tree_params.hash_alg, req);
|
||||||
|
|
||||||
return valid;
|
return valid;
|
||||||
}
|
}
|
||||||
|
@ -229,12 +228,8 @@ void fsverity_verify_bio(struct bio *bio)
|
||||||
int i;
|
int i;
|
||||||
unsigned long max_ra_pages = 0;
|
unsigned long max_ra_pages = 0;
|
||||||
|
|
||||||
req = ahash_request_alloc(params->hash_alg->tfm, GFP_NOFS);
|
/* This allocation never fails, since it's mempool-backed. */
|
||||||
if (unlikely(!req)) {
|
req = fsverity_alloc_hash_request(params->hash_alg, GFP_NOFS);
|
||||||
bio_for_each_segment_all(bv, bio, i)
|
|
||||||
SetPageError(bv->bv_page);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bio->bi_opf & REQ_RAHEAD) {
|
if (bio->bi_opf & REQ_RAHEAD) {
|
||||||
/*
|
/*
|
||||||
|
@ -262,7 +257,7 @@ void fsverity_verify_bio(struct bio *bio)
|
||||||
SetPageError(page);
|
SetPageError(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
ahash_request_free(req);
|
fsverity_free_hash_request(params->hash_alg, req);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(fsverity_verify_bio);
|
EXPORT_SYMBOL_GPL(fsverity_verify_bio);
|
||||||
#endif /* CONFIG_BLOCK */
|
#endif /* CONFIG_BLOCK */
|
||||||
|
|
Loading…
Reference in a new issue