dm-verity: use SHA-256 library for SHA-256

When the hash algorithm is SHA-256 and the verity version is not 0, use
the SHA-256 library instead of crypto_shash.

This is a prerequisite for making dm-verity interleave the computation
of SHA-256 hashes for increased performance.  That optimization is
available in the SHA-256 library but not in crypto_shash.

Even without interleaved hashing, switching to the library also slightly
improves performance by itself because it avoids the overhead of
crypto_shash, including indirect calls and other API overhead.
(Benchmark on x86_64, AMD Zen 5: hashing 4K blocks gets 2.1% faster.)

SHA-256 is by far the most common hash algorithm used with dm-verity.
It makes sense to optimize for the common case and fall back to the
generic crypto layer for uncommon cases, as suggested by Linus:
https://lore.kernel.org/r/CAHk-=wgp-fOSsZsYrbyzqCAfEvrt5jQs1jL-97Wc4seMNTUyng@mail.gmail.com

Signed-off-by: Eric Biggers <ebiggers@kernel.org>
Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
This commit is contained in:
Eric Biggers
2025-10-14 14:16:55 -07:00
committed by Mikulas Patocka
parent 3ee6c4bc53
commit ba0f428c9b
3 changed files with 64 additions and 18 deletions

View File

@@ -547,6 +547,7 @@ config DM_VERITY
depends on BLK_DEV_DM
select CRYPTO
select CRYPTO_HASH
select CRYPTO_LIB_SHA256
select DM_BUFIO
help
This device-mapper target creates a read-only device that

View File

@@ -117,11 +117,25 @@ static sector_t verity_position_at_level(struct dm_verity *v, sector_t block,
int verity_hash(struct dm_verity *v, struct dm_verity_io *io,
const u8 *data, size_t len, u8 *digest)
{
struct shash_desc *desc = &io->hash_desc;
struct shash_desc *desc;
int r;
if (likely(v->use_sha256_lib)) {
struct sha256_ctx *ctx = &io->hash_ctx.sha256;
/*
* Fast path using SHA-256 library. This is enabled only for
* verity version 1, where the salt is at the beginning.
*/
*ctx = *v->initial_hashstate.sha256;
sha256_update(ctx, data, len);
sha256_final(ctx, digest);
return 0;
}
desc = &io->hash_ctx.shash;
desc->tfm = v->shash_tfm;
if (unlikely(v->initial_hashstate == NULL)) {
if (unlikely(v->initial_hashstate.shash == NULL)) {
/* Version 0: salt at end */
r = crypto_shash_init(desc) ?:
crypto_shash_update(desc, data, len) ?:
@@ -129,7 +143,7 @@ int verity_hash(struct dm_verity *v, struct dm_verity_io *io,
crypto_shash_final(desc, digest);
} else {
/* Version 1: salt at beginning */
r = crypto_shash_import(desc, v->initial_hashstate) ?:
r = crypto_shash_import(desc, v->initial_hashstate.shash) ?:
crypto_shash_finup(desc, data, len, digest);
}
if (unlikely(r))
@@ -1004,7 +1018,7 @@ static void verity_dtr(struct dm_target *ti)
kvfree(v->validated_blocks);
kfree(v->salt);
kfree(v->initial_hashstate);
kfree(v->initial_hashstate.shash);
kfree(v->root_digest);
kfree(v->zero_digest);
verity_free_sig(v);
@@ -1069,8 +1083,7 @@ static int verity_alloc_zero_digest(struct dm_verity *v)
if (!v->zero_digest)
return r;
io = kmalloc(sizeof(*io) + crypto_shash_descsize(v->shash_tfm),
GFP_KERNEL);
io = kmalloc(v->ti->per_io_data_size, GFP_KERNEL);
if (!io)
return r; /* verity_dtr will free zero_digest */
@@ -1256,6 +1269,20 @@ static int verity_setup_hash_alg(struct dm_verity *v, const char *alg_name)
ti->error = "Digest size too big";
return -EINVAL;
}
if (likely(v->version && strcmp(alg_name, "sha256") == 0)) {
/*
* Fast path: use the library API for reduced overhead and
* interleaved hashing support.
*/
v->use_sha256_lib = true;
ti->per_io_data_size =
offsetofend(struct dm_verity_io, hash_ctx.sha256);
} else {
/* Fallback case: use the generic crypto API. */
ti->per_io_data_size =
offsetofend(struct dm_verity_io, hash_ctx.shash) +
crypto_shash_descsize(shash);
}
return 0;
}
@@ -1276,7 +1303,18 @@ static int verity_setup_salt_and_hashstate(struct dm_verity *v, const char *arg)
return -EINVAL;
}
}
if (v->version) { /* Version 1: salt at beginning */
if (likely(v->use_sha256_lib)) {
/* Implies version 1: salt at beginning */
v->initial_hashstate.sha256 =
kmalloc(sizeof(struct sha256_ctx), GFP_KERNEL);
if (!v->initial_hashstate.sha256) {
ti->error = "Cannot allocate initial hash state";
return -ENOMEM;
}
sha256_init(v->initial_hashstate.sha256);
sha256_update(v->initial_hashstate.sha256,
v->salt, v->salt_size);
} else if (v->version) { /* Version 1: salt at beginning */
SHASH_DESC_ON_STACK(desc, v->shash_tfm);
int r;
@@ -1284,16 +1322,16 @@ static int verity_setup_salt_and_hashstate(struct dm_verity *v, const char *arg)
* Compute the pre-salted hash state that can be passed to
* crypto_shash_import() for each block later.
*/
v->initial_hashstate = kmalloc(
v->initial_hashstate.shash = kmalloc(
crypto_shash_statesize(v->shash_tfm), GFP_KERNEL);
if (!v->initial_hashstate) {
if (!v->initial_hashstate.shash) {
ti->error = "Cannot allocate initial hash state";
return -ENOMEM;
}
desc->tfm = v->shash_tfm;
r = crypto_shash_init(desc) ?:
crypto_shash_update(desc, v->salt, v->salt_size) ?:
crypto_shash_export(desc, v->initial_hashstate);
crypto_shash_export(desc, v->initial_hashstate.shash);
if (r) {
ti->error = "Cannot set up initial hash state";
return r;
@@ -1555,9 +1593,6 @@ static int verity_ctr(struct dm_target *ti, unsigned int argc, char **argv)
goto bad;
}
ti->per_io_data_size = sizeof(struct dm_verity_io) +
crypto_shash_descsize(v->shash_tfm);
r = verity_fec_ctr(v);
if (r)
goto bad;

View File

@@ -16,6 +16,7 @@
#include <linux/device-mapper.h>
#include <linux/interrupt.h>
#include <crypto/hash.h>
#include <crypto/sha2.h>
#define DM_VERITY_MAX_LEVELS 63
@@ -42,7 +43,10 @@ struct dm_verity {
struct crypto_shash *shash_tfm;
u8 *root_digest; /* digest of the root block */
u8 *salt; /* salt: its size is salt_size */
u8 *initial_hashstate; /* salted initial state, if version >= 1 */
union {
struct sha256_ctx *sha256; /* for use_sha256_lib=1 */
u8 *shash; /* for use_sha256_lib=0 */
} initial_hashstate; /* salted initial state, if version >= 1 */
u8 *zero_digest; /* digest for a zero block */
#ifdef CONFIG_SECURITY
u8 *root_digest_sig; /* signature of the root digest */
@@ -59,6 +63,7 @@ struct dm_verity {
unsigned char version;
bool hash_failed:1; /* set if hash of any block failed */
bool use_bh_wq:1; /* try to verify in BH wq before normal work-queue */
bool use_sha256_lib:1; /* use SHA-256 library instead of generic crypto API */
unsigned int digest_size; /* digest size for the current hash algorithm */
enum verity_mode mode; /* mode for handling verification errors */
enum verity_mode error_mode;/* mode for handling I/O errors */
@@ -98,11 +103,16 @@ struct dm_verity_io {
u8 want_digest[HASH_MAX_DIGESTSIZE];
/*
* Temporary space for hashing. This is variable-length and must be at
* the end of the struct. struct shash_desc is just the fixed part;
* it's followed by a context of size crypto_shash_descsize(shash_tfm).
* Temporary space for hashing. Either sha256 or shash is used,
* depending on the value of use_sha256_lib. If shash is used,
* then this field is variable-length, with total size
* sizeof(struct shash_desc) + crypto_shash_descsize(shash_tfm).
* For this reason, this field must be the end of the struct.
*/
struct shash_desc hash_desc;
union {
struct sha256_ctx sha256;
struct shash_desc shash;
} hash_ctx;
};
static inline u8 *verity_io_real_digest(struct dm_verity *v,