fsverity: Switch from crypto_shash to SHA-2 library

fsverity supports two hash algorithms: SHA-256 and SHA-512.  Since both
of these have a library API now, just use the library API instead of
crypto_shash.  Even with multiple algorithms, the library-based code
still ends up being quite a bit simpler, due to how clumsy the
old-school crypto API is.  The library-based code is also more
efficient, since it avoids overheads such as indirect calls.

Acked-by: Ard Biesheuvel <ardb@kernel.org>
Link: https://lore.kernel.org/r/20250630172224.46909-3-ebiggers@kernel.org
Signed-off-by: Eric Biggers <ebiggers@kernel.org>
This commit is contained in:
Eric Biggers 2025-06-30 10:22:24 -07:00
parent b309bf7c3e
commit 998646b3c1
7 changed files with 100 additions and 174 deletions

View file

@ -185,8 +185,7 @@ FS_IOC_ENABLE_VERITY can fail with the following errors:
- ``ENOKEY``: the ".fs-verity" keyring doesn't contain the certificate - ``ENOKEY``: the ".fs-verity" keyring doesn't contain the certificate
needed to verify the builtin signature needed to verify the builtin signature
- ``ENOPKG``: fs-verity recognizes the hash algorithm, but it's not - ``ENOPKG``: fs-verity recognizes the hash algorithm, but it's not
available in the kernel's crypto API as currently configured (e.g. available in the kernel as currently configured
for SHA-512, missing CONFIG_CRYPTO_SHA512).
- ``ENOTTY``: this type of filesystem does not implement fs-verity - ``ENOTTY``: this type of filesystem does not implement fs-verity
- ``EOPNOTSUPP``: the kernel was not configured with fs-verity - ``EOPNOTSUPP``: the kernel was not configured with fs-verity
support; or the filesystem superblock has not had the 'verity' support; or the filesystem superblock has not had the 'verity'

View file

@ -2,11 +2,9 @@
config FS_VERITY config FS_VERITY
bool "FS Verity (read-only file-based authenticity protection)" bool "FS Verity (read-only file-based authenticity protection)"
select CRYPTO
select CRYPTO_HASH_INFO select CRYPTO_HASH_INFO
# SHA-256 is selected as it's intended to be the default hash algorithm. select CRYPTO_LIB_SHA256
# To avoid bloat, other wanted algorithms must be selected explicitly. select CRYPTO_LIB_SHA512
select CRYPTO_SHA256
help help
This option enables fs-verity. fs-verity is the dm-verity This option enables fs-verity. fs-verity is the dm-verity
mechanism implemented at the file level. On supported mechanism implemented at the file level. On supported

View file

@ -7,7 +7,6 @@
#include "fsverity_private.h" #include "fsverity_private.h"
#include <crypto/hash.h>
#include <linux/export.h> #include <linux/export.h>
#include <linux/mount.h> #include <linux/mount.h>
#include <linux/sched/signal.h> #include <linux/sched/signal.h>
@ -25,7 +24,6 @@ static int hash_one_block(struct inode *inode,
struct block_buffer *cur) struct block_buffer *cur)
{ {
struct block_buffer *next = cur + 1; struct block_buffer *next = cur + 1;
int err;
/* /*
* Safety check to prevent a buffer overflow in case of a filesystem bug * Safety check to prevent a buffer overflow in case of a filesystem bug
@ -38,10 +36,8 @@ static int hash_one_block(struct inode *inode,
/* Zero-pad the block if it's shorter than the block size. */ /* Zero-pad the block if it's shorter than the block size. */
memset(&cur->data[cur->filled], 0, params->block_size - cur->filled); memset(&cur->data[cur->filled], 0, params->block_size - cur->filled);
err = fsverity_hash_block(params, inode, cur->data, fsverity_hash_block(params, inode, cur->data,
&next->data[next->filled]); &next->data[next->filled]);
if (err)
return err;
next->filled += params->digest_size; next->filled += params->digest_size;
cur->filled = 0; cur->filled = 0;
return 0; return 0;

View file

@ -20,7 +20,6 @@
/* A hash algorithm supported by fs-verity */ /* A hash algorithm supported by fs-verity */
struct fsverity_hash_alg { struct fsverity_hash_alg {
struct crypto_shash *tfm; /* hash tfm, allocated on demand */
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 */
@ -31,10 +30,16 @@ struct fsverity_hash_alg {
enum hash_algo algo_id; enum hash_algo algo_id;
}; };
union fsverity_hash_ctx {
struct sha256_ctx sha256;
struct sha512_ctx sha512;
};
/* 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 */ const struct fsverity_hash_alg *hash_alg; /* the hash algorithm */
const u8 *hashstate; /* initial hash state or NULL */ /* initial hash state if salted, NULL if unsalted */
const union fsverity_hash_ctx *hashstate;
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 */
unsigned int hashes_per_block; /* number of hashes per tree block */ unsigned int hashes_per_block; /* number of hashes per tree block */
@ -76,15 +81,16 @@ struct fsverity_info {
/* hash_algs.c */ /* hash_algs.c */
extern struct fsverity_hash_alg fsverity_hash_algs[]; extern const struct fsverity_hash_alg fsverity_hash_algs[];
const struct fsverity_hash_alg *fsverity_get_hash_alg(const struct inode *inode, const 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, union fsverity_hash_ctx *
fsverity_prepare_hash_state(const struct fsverity_hash_alg *alg,
const u8 *salt, size_t salt_size); const u8 *salt, size_t salt_size);
int fsverity_hash_block(const struct merkle_tree_params *params, void fsverity_hash_block(const struct merkle_tree_params *params,
const struct inode *inode, const void *data, u8 *out); const struct inode *inode, const void *data, u8 *out);
int fsverity_hash_buffer(const struct fsverity_hash_alg *alg, void fsverity_hash_buffer(const 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);

View file

@ -7,10 +7,8 @@
#include "fsverity_private.h" #include "fsverity_private.h"
#include <crypto/hash.h>
/* The hash algorithms supported by fs-verity */ /* The hash algorithms supported by fs-verity */
struct fsverity_hash_alg fsverity_hash_algs[] = { const struct fsverity_hash_alg fsverity_hash_algs[] = {
[FS_VERITY_HASH_ALG_SHA256] = { [FS_VERITY_HASH_ALG_SHA256] = {
.name = "sha256", .name = "sha256",
.digest_size = SHA256_DIGEST_SIZE, .digest_size = SHA256_DIGEST_SIZE,
@ -25,106 +23,42 @@ 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() - get a hash algorithm by number
* @inode: optional inode for logging purposes * @inode: optional inode for logging purposes
* @num: the hash algorithm number * @num: the hash algorithm number
* *
* Get the struct fsverity_hash_alg for the given hash algorithm number, and * Get the struct fsverity_hash_alg for the given hash algorithm number.
* ensure it has a hash transform ready to go. The hash transforms are
* allocated on-demand so that we don't waste resources unnecessarily, and
* because the crypto modules may be initialized later than fs/verity/.
* *
* Return: pointer to the hash alg on success, else an ERR_PTR() * Return: pointer to the hash alg if it's known, otherwise NULL.
*/ */
const struct fsverity_hash_alg *fsverity_get_hash_alg(const struct inode *inode, const struct fsverity_hash_alg *fsverity_get_hash_alg(const struct inode *inode,
unsigned int num) unsigned int num)
{ {
struct fsverity_hash_alg *alg;
struct crypto_shash *tfm;
int err;
if (num >= ARRAY_SIZE(fsverity_hash_algs) || if (num >= ARRAY_SIZE(fsverity_hash_algs) ||
!fsverity_hash_algs[num].name) { !fsverity_hash_algs[num].name) {
fsverity_warn(inode, "Unknown hash algorithm number: %u", num); fsverity_warn(inode, "Unknown hash algorithm number: %u", num);
return ERR_PTR(-EINVAL); return NULL;
} }
alg = &fsverity_hash_algs[num]; return &fsverity_hash_algs[num];
/* pairs with smp_store_release() below */
if (likely(smp_load_acquire(&alg->tfm) != NULL))
return alg;
mutex_lock(&fsverity_hash_alg_init_mutex);
if (alg->tfm != NULL)
goto out_unlock;
tfm = crypto_alloc_shash(alg->name, 0, 0);
if (IS_ERR(tfm)) {
if (PTR_ERR(tfm) == -ENOENT) {
fsverity_warn(inode,
"Missing crypto API support for hash algorithm \"%s\"",
alg->name);
alg = ERR_PTR(-ENOPKG);
goto out_unlock;
}
fsverity_err(inode,
"Error allocating hash algorithm \"%s\": %ld",
alg->name, PTR_ERR(tfm));
alg = ERR_CAST(tfm);
goto out_unlock;
}
err = -EINVAL;
if (WARN_ON_ONCE(alg->digest_size != crypto_shash_digestsize(tfm)))
goto err_free_tfm;
if (WARN_ON_ONCE(alg->block_size != crypto_shash_blocksize(tfm)))
goto err_free_tfm;
pr_info("%s using implementation \"%s\"\n",
alg->name, crypto_shash_driver_name(tfm));
/* pairs with smp_load_acquire() above */
smp_store_release(&alg->tfm, tfm);
goto out_unlock;
err_free_tfm:
crypto_free_shash(tfm);
alg = ERR_PTR(err);
out_unlock:
mutex_unlock(&fsverity_hash_alg_init_mutex);
return alg;
} }
/** /**
* fsverity_prepare_hash_state() - precompute the initial hash state * fsverity_prepare_hash_state() - precompute the initial hash state
* @alg: hash algorithm * @alg: hash algorithm
* @salt: a salt which is to be prepended to all data to be hashed * @salt: a salt which is to be prepended to all data to be hashed
* @salt_size: salt size in bytes, possibly 0 * @salt_size: salt size in bytes
* *
* Return: NULL if the salt is empty, otherwise the kmalloc()'ed precomputed * Return: the kmalloc()'ed initial hash state, or NULL if out of memory.
* initial hash state on success or an ERR_PTR() on failure.
*/ */
const u8 *fsverity_prepare_hash_state(const struct fsverity_hash_alg *alg, union fsverity_hash_ctx *
fsverity_prepare_hash_state(const struct fsverity_hash_alg *alg,
const u8 *salt, size_t salt_size) const u8 *salt, size_t salt_size)
{ {
u8 *hashstate = NULL;
SHASH_DESC_ON_STACK(desc, alg->tfm);
u8 *padded_salt = NULL; u8 *padded_salt = NULL;
size_t padded_salt_size; size_t padded_salt_size;
int err; union fsverity_hash_ctx ctx;
void *res = NULL;
desc->tfm = alg->tfm;
if (salt_size == 0)
return NULL;
hashstate = kmalloc(crypto_shash_statesize(alg->tfm), GFP_KERNEL);
if (!hashstate)
return ERR_PTR(-ENOMEM);
/* /*
* 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
@ -135,30 +69,26 @@ const u8 *fsverity_prepare_hash_state(const struct fsverity_hash_alg *alg,
*/ */
padded_salt_size = round_up(salt_size, alg->block_size); padded_salt_size = round_up(salt_size, alg->block_size);
padded_salt = kzalloc(padded_salt_size, GFP_KERNEL); padded_salt = kzalloc(padded_salt_size, GFP_KERNEL);
if (!padded_salt) { if (!padded_salt)
err = -ENOMEM; return NULL;
goto err_free;
}
memcpy(padded_salt, salt, salt_size); memcpy(padded_salt, salt, salt_size);
err = crypto_shash_init(desc);
if (err)
goto err_free;
err = crypto_shash_update(desc, padded_salt, padded_salt_size); switch (alg->algo_id) {
if (err) case HASH_ALGO_SHA256:
goto err_free; sha256_init(&ctx.sha256);
sha256_update(&ctx.sha256, padded_salt, padded_salt_size);
err = crypto_shash_export(desc, hashstate); res = kmemdup(&ctx.sha256, sizeof(ctx.sha256), GFP_KERNEL);
if (err) break;
goto err_free; case HASH_ALGO_SHA512:
out: sha512_init(&ctx.sha512);
sha512_update(&ctx.sha512, padded_salt, padded_salt_size);
res = kmemdup(&ctx.sha512, sizeof(ctx.sha512), GFP_KERNEL);
break;
default:
WARN_ON_ONCE(1);
}
kfree(padded_salt); kfree(padded_salt);
return hashstate; return res;
err_free:
kfree(hashstate);
hashstate = ERR_PTR(err);
goto out;
} }
/** /**
@ -170,31 +100,32 @@ err_free:
* *
* Hash a single data or hash block. The hash is salted if a salt is specified * Hash a single data or hash block. The hash is salted if a salt is specified
* in the Merkle tree parameters. * in the Merkle tree parameters.
*
* Return: 0 on success, -errno on failure
*/ */
int fsverity_hash_block(const struct merkle_tree_params *params, void fsverity_hash_block(const struct merkle_tree_params *params,
const struct inode *inode, const void *data, u8 *out) const struct inode *inode, const void *data, u8 *out)
{ {
SHASH_DESC_ON_STACK(desc, params->hash_alg->tfm); union fsverity_hash_ctx ctx;
int err;
desc->tfm = params->hash_alg->tfm; if (!params->hashstate) {
fsverity_hash_buffer(params->hash_alg, data, params->block_size,
out);
return;
}
if (params->hashstate) { switch (params->hash_alg->algo_id) {
err = crypto_shash_import(desc, params->hashstate); case HASH_ALGO_SHA256:
if (err) { ctx.sha256 = params->hashstate->sha256;
fsverity_err(inode, sha256_update(&ctx.sha256, data, params->block_size);
"Error %d importing hash state", err); sha256_final(&ctx.sha256, out);
return err; return;
case HASH_ALGO_SHA512:
ctx.sha512 = params->hashstate->sha512;
sha512_update(&ctx.sha512, data, params->block_size);
sha512_final(&ctx.sha512, out);
return;
default:
BUG();
} }
err = crypto_shash_finup(desc, data, params->block_size, out);
} else {
err = crypto_shash_digest(desc, data, params->block_size, out);
}
if (err)
fsverity_err(inode, "Error %d computing block hash", err);
return err;
} }
/** /**
@ -203,13 +134,20 @@ int fsverity_hash_block(const struct merkle_tree_params *params,
* @data: the data to hash * @data: the data to hash
* @size: size of data to hash, in bytes * @size: size of data to hash, in bytes
* @out: output digest, size 'alg->digest_size' bytes * @out: output digest, size 'alg->digest_size' bytes
*
* Return: 0 on success, -errno on failure
*/ */
int fsverity_hash_buffer(const struct fsverity_hash_alg *alg, void fsverity_hash_buffer(const struct fsverity_hash_alg *alg,
const void *data, size_t size, u8 *out) const void *data, size_t size, u8 *out)
{ {
return crypto_shash_tfm_digest(alg->tfm, data, size, out); switch (alg->algo_id) {
case HASH_ALGO_SHA256:
sha256(data, size, out);
return;
case HASH_ALGO_SHA512:
sha512(data, size, out);
return;
default:
BUG();
}
} }
void __init fsverity_check_hash_algs(void) void __init fsverity_check_hash_algs(void)

View file

@ -43,19 +43,19 @@ int fsverity_init_merkle_tree_params(struct merkle_tree_params *params,
memset(params, 0, sizeof(*params)); memset(params, 0, sizeof(*params));
hash_alg = fsverity_get_hash_alg(inode, hash_algorithm); hash_alg = fsverity_get_hash_alg(inode, hash_algorithm);
if (IS_ERR(hash_alg)) if (!hash_alg)
return PTR_ERR(hash_alg); return -EINVAL;
params->hash_alg = hash_alg; params->hash_alg = hash_alg;
params->digest_size = hash_alg->digest_size; params->digest_size = hash_alg->digest_size;
params->hashstate = fsverity_prepare_hash_state(hash_alg, salt, if (salt_size) {
salt_size); params->hashstate =
if (IS_ERR(params->hashstate)) { fsverity_prepare_hash_state(hash_alg, salt, salt_size);
err = PTR_ERR(params->hashstate); if (!params->hashstate) {
params->hashstate = NULL; err = -ENOMEM;
fsverity_err(inode, "Error %d preparing hash state", err);
goto out_err; goto out_err;
} }
}
/* /*
* fs/verity/ directly assumes that the Merkle tree block size is a * fs/verity/ directly assumes that the Merkle tree block size is a
@ -159,18 +159,15 @@ out_err:
* Compute the file digest by hashing the fsverity_descriptor excluding the * Compute the file digest by hashing the fsverity_descriptor excluding the
* builtin signature and with the sig_size field set to 0. * builtin signature and with the sig_size field set to 0.
*/ */
static int compute_file_digest(const struct fsverity_hash_alg *hash_alg, static void compute_file_digest(const struct fsverity_hash_alg *hash_alg,
struct fsverity_descriptor *desc, struct fsverity_descriptor *desc,
u8 *file_digest) u8 *file_digest)
{ {
__le32 sig_size = desc->sig_size; __le32 sig_size = desc->sig_size;
int err;
desc->sig_size = 0; desc->sig_size = 0;
err = fsverity_hash_buffer(hash_alg, desc, sizeof(*desc), file_digest); fsverity_hash_buffer(hash_alg, desc, sizeof(*desc), file_digest);
desc->sig_size = sig_size; desc->sig_size = sig_size;
return err;
} }
/* /*
@ -202,12 +199,7 @@ struct fsverity_info *fsverity_create_info(const struct inode *inode,
memcpy(vi->root_hash, desc->root_hash, vi->tree_params.digest_size); memcpy(vi->root_hash, desc->root_hash, vi->tree_params.digest_size);
err = compute_file_digest(vi->tree_params.hash_alg, desc, compute_file_digest(vi->tree_params.hash_alg, desc, vi->file_digest);
vi->file_digest);
if (err) {
fsverity_err(inode, "Error %d computing file digest", err);
goto fail;
}
err = fsverity_verify_signature(vi, desc->signature, err = fsverity_verify_signature(vi, desc->signature,
le32_to_cpu(desc->sig_size)); le32_to_cpu(desc->sig_size));

View file

@ -7,7 +7,6 @@
#include "fsverity_private.h" #include "fsverity_private.h"
#include <crypto/hash.h>
#include <linux/bio.h> #include <linux/bio.h>
#include <linux/export.h> #include <linux/export.h>
@ -203,8 +202,7 @@ descend:
unsigned long hblock_idx = hblocks[level - 1].index; unsigned long hblock_idx = hblocks[level - 1].index;
unsigned int hoffset = hblocks[level - 1].hoffset; unsigned int hoffset = hblocks[level - 1].hoffset;
if (fsverity_hash_block(params, inode, haddr, real_hash) != 0) fsverity_hash_block(params, inode, haddr, real_hash);
goto error;
if (memcmp(want_hash, real_hash, hsize) != 0) if (memcmp(want_hash, real_hash, hsize) != 0)
goto corrupted; goto corrupted;
/* /*
@ -223,8 +221,7 @@ descend:
} }
/* Finally, verify the data block. */ /* Finally, verify the data block. */
if (fsverity_hash_block(params, inode, data, real_hash) != 0) fsverity_hash_block(params, inode, data, real_hash);
goto error;
if (memcmp(want_hash, real_hash, hsize) != 0) if (memcmp(want_hash, real_hash, hsize) != 0)
goto corrupted; goto corrupted;
return true; return true;