Skip to content

Commit a1f692f

Browse files
author
Eric Biggers
committed
fsverity: Use 2-way interleaved SHA-256 hashing when supported
When the crypto library provides an optimized implementation of sha256_finup_2x(), use it to interleave the hashing of pairs of data blocks. On some CPUs this nearly doubles hashing performance. The increase in overall throughput of cold-cache fsverity reads that I'm seeing on arm64 and x86_64 is roughly 35% (though this metric is hard to measure as it jumps around a lot). For now this is only done on the verification path, and only for data blocks, not Merkle tree blocks. We could use sha256_finup_2x() on Merkle tree blocks too, but that is less important as there aren't as many Merkle tree blocks as data blocks, and that would require some additional code restructuring. We could also use sha256_finup_2x() to accelerate building the Merkle tree, but verification performance is more important. Reviewed-by: Ard Biesheuvel <ardb@kernel.org> Link: https://lore.kernel.org/r/20250915160819.140019-7-ebiggers@kernel.org Signed-off-by: Eric Biggers <ebiggers@kernel.org>
1 parent 4bd70b5 commit a1f692f

1 file changed

Lines changed: 139 additions & 34 deletions

File tree

fs/verity/verify.c

Lines changed: 139 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,31 @@
1010
#include <linux/bio.h>
1111
#include <linux/export.h>
1212

13+
#define FS_VERITY_MAX_PENDING_BLOCKS 2
14+
15+
struct fsverity_pending_block {
16+
const void *data;
17+
u64 pos;
18+
u8 real_hash[FS_VERITY_MAX_DIGEST_SIZE];
19+
};
20+
21+
struct fsverity_verification_context {
22+
struct inode *inode;
23+
struct fsverity_info *vi;
24+
unsigned long max_ra_pages;
25+
26+
/*
27+
* This is the queue of data blocks that are pending verification. When
28+
* the crypto layer supports interleaved hashing, we allow multiple
29+
* blocks to be queued up in order to utilize it. This can improve
30+
* performance significantly vs. sequential hashing of each block.
31+
*/
32+
int num_pending;
33+
int max_pending;
34+
struct fsverity_pending_block
35+
pending_blocks[FS_VERITY_MAX_PENDING_BLOCKS];
36+
};
37+
1338
static struct workqueue_struct *fsverity_read_workqueue;
1439

1540
/*
@@ -79,7 +104,7 @@ static bool is_hash_block_verified(struct fsverity_info *vi, struct page *hpage,
79104
}
80105

81106
/*
82-
* Verify a single data block against the file's Merkle tree.
107+
* Verify the hash of a single data block against the file's Merkle tree.
83108
*
84109
* In principle, we need to verify the entire path to the root node. However,
85110
* for efficiency the filesystem may cache the hash blocks. Therefore we need
@@ -88,10 +113,11 @@ static bool is_hash_block_verified(struct fsverity_info *vi, struct page *hpage,
88113
*
89114
* Return: %true if the data block is valid, else %false.
90115
*/
91-
static bool
92-
verify_data_block(struct inode *inode, struct fsverity_info *vi,
93-
const void *data, u64 data_pos, unsigned long max_ra_pages)
116+
static bool verify_data_block(struct inode *inode, struct fsverity_info *vi,
117+
const struct fsverity_pending_block *dblock,
118+
unsigned long max_ra_pages)
94119
{
120+
const u64 data_pos = dblock->pos;
95121
const struct merkle_tree_params *params = &vi->tree_params;
96122
const unsigned int hsize = params->digest_size;
97123
int level;
@@ -115,8 +141,12 @@ verify_data_block(struct inode *inode, struct fsverity_info *vi,
115141
*/
116142
u64 hidx = data_pos >> params->log_blocksize;
117143

118-
/* Up to 1 + FS_VERITY_MAX_LEVELS pages may be mapped at once */
119-
BUILD_BUG_ON(1 + FS_VERITY_MAX_LEVELS > KM_MAX_IDX);
144+
/*
145+
* Up to FS_VERITY_MAX_PENDING_BLOCKS + FS_VERITY_MAX_LEVELS pages may
146+
* be mapped at once.
147+
*/
148+
static_assert(FS_VERITY_MAX_PENDING_BLOCKS + FS_VERITY_MAX_LEVELS <=
149+
KM_MAX_IDX);
120150

121151
if (unlikely(data_pos >= inode->i_size)) {
122152
/*
@@ -127,7 +157,7 @@ verify_data_block(struct inode *inode, struct fsverity_info *vi,
127157
* any part past EOF should be all zeroes. Therefore, we need
128158
* to verify that any data blocks fully past EOF are all zeroes.
129159
*/
130-
if (memchr_inv(data, 0, params->block_size)) {
160+
if (memchr_inv(dblock->data, 0, params->block_size)) {
131161
fsverity_err(inode,
132162
"FILE CORRUPTED! Data past EOF is not zeroed");
133163
return false;
@@ -220,18 +250,18 @@ verify_data_block(struct inode *inode, struct fsverity_info *vi,
220250
put_page(hpage);
221251
}
222252

223-
/* Finally, verify the data block. */
224-
fsverity_hash_block(params, data, real_hash);
225-
if (memcmp(want_hash, real_hash, hsize) != 0)
253+
/* Finally, verify the hash of the data block. */
254+
if (memcmp(want_hash, dblock->real_hash, hsize) != 0)
226255
goto corrupted;
227256
return true;
228257

229258
corrupted:
230-
fsverity_err(inode,
231-
"FILE CORRUPTED! pos=%llu, level=%d, want_hash=%s:%*phN, real_hash=%s:%*phN",
232-
data_pos, level - 1,
233-
params->hash_alg->name, hsize, want_hash,
234-
params->hash_alg->name, hsize, real_hash);
259+
fsverity_err(
260+
inode,
261+
"FILE CORRUPTED! pos=%llu, level=%d, want_hash=%s:%*phN, real_hash=%s:%*phN",
262+
data_pos, level - 1, params->hash_alg->name, hsize, want_hash,
263+
params->hash_alg->name, hsize,
264+
level == 0 ? dblock->real_hash : real_hash);
235265
error:
236266
for (; level > 0; level--) {
237267
kunmap_local(hblocks[level - 1].addr);
@@ -240,13 +270,73 @@ verify_data_block(struct inode *inode, struct fsverity_info *vi,
240270
return false;
241271
}
242272

243-
static bool
244-
verify_data_blocks(struct folio *data_folio, size_t len, size_t offset,
245-
unsigned long max_ra_pages)
273+
static void
274+
fsverity_init_verification_context(struct fsverity_verification_context *ctx,
275+
struct inode *inode,
276+
unsigned long max_ra_pages)
246277
{
247-
struct inode *inode = data_folio->mapping->host;
248278
struct fsverity_info *vi = *fsverity_info_addr(inode);
249-
const unsigned int block_size = vi->tree_params.block_size;
279+
280+
ctx->inode = inode;
281+
ctx->vi = vi;
282+
ctx->max_ra_pages = max_ra_pages;
283+
ctx->num_pending = 0;
284+
if (vi->tree_params.hash_alg->algo_id == HASH_ALGO_SHA256 &&
285+
sha256_finup_2x_is_optimized())
286+
ctx->max_pending = 2;
287+
else
288+
ctx->max_pending = 1;
289+
}
290+
291+
static void
292+
fsverity_clear_pending_blocks(struct fsverity_verification_context *ctx)
293+
{
294+
int i;
295+
296+
for (i = ctx->num_pending - 1; i >= 0; i--) {
297+
kunmap_local(ctx->pending_blocks[i].data);
298+
ctx->pending_blocks[i].data = NULL;
299+
}
300+
ctx->num_pending = 0;
301+
}
302+
303+
static bool
304+
fsverity_verify_pending_blocks(struct fsverity_verification_context *ctx)
305+
{
306+
struct fsverity_info *vi = ctx->vi;
307+
const struct merkle_tree_params *params = &vi->tree_params;
308+
int i;
309+
310+
if (ctx->num_pending == 2) {
311+
/* num_pending == 2 implies that the algorithm is SHA-256 */
312+
sha256_finup_2x(params->hashstate ? &params->hashstate->sha256 :
313+
NULL,
314+
ctx->pending_blocks[0].data,
315+
ctx->pending_blocks[1].data, params->block_size,
316+
ctx->pending_blocks[0].real_hash,
317+
ctx->pending_blocks[1].real_hash);
318+
} else {
319+
for (i = 0; i < ctx->num_pending; i++)
320+
fsverity_hash_block(params, ctx->pending_blocks[i].data,
321+
ctx->pending_blocks[i].real_hash);
322+
}
323+
324+
for (i = 0; i < ctx->num_pending; i++) {
325+
if (!verify_data_block(ctx->inode, vi, &ctx->pending_blocks[i],
326+
ctx->max_ra_pages))
327+
return false;
328+
}
329+
fsverity_clear_pending_blocks(ctx);
330+
return true;
331+
}
332+
333+
static bool fsverity_add_data_blocks(struct fsverity_verification_context *ctx,
334+
struct folio *data_folio, size_t len,
335+
size_t offset)
336+
{
337+
struct fsverity_info *vi = ctx->vi;
338+
const struct merkle_tree_params *params = &vi->tree_params;
339+
const unsigned int block_size = params->block_size;
250340
u64 pos = (u64)data_folio->index << PAGE_SHIFT;
251341

252342
if (WARN_ON_ONCE(len <= 0 || !IS_ALIGNED(len | offset, block_size)))
@@ -255,14 +345,11 @@ verify_data_blocks(struct folio *data_folio, size_t len, size_t offset,
255345
folio_test_uptodate(data_folio)))
256346
return false;
257347
do {
258-
void *data;
259-
bool valid;
260-
261-
data = kmap_local_folio(data_folio, offset);
262-
valid = verify_data_block(inode, vi, data, pos + offset,
263-
max_ra_pages);
264-
kunmap_local(data);
265-
if (!valid)
348+
ctx->pending_blocks[ctx->num_pending].data =
349+
kmap_local_folio(data_folio, offset);
350+
ctx->pending_blocks[ctx->num_pending].pos = pos + offset;
351+
if (++ctx->num_pending == ctx->max_pending &&
352+
!fsverity_verify_pending_blocks(ctx))
266353
return false;
267354
offset += block_size;
268355
len -= block_size;
@@ -284,7 +371,15 @@ verify_data_blocks(struct folio *data_folio, size_t len, size_t offset,
284371
*/
285372
bool fsverity_verify_blocks(struct folio *folio, size_t len, size_t offset)
286373
{
287-
return verify_data_blocks(folio, len, offset, 0);
374+
struct fsverity_verification_context ctx;
375+
376+
fsverity_init_verification_context(&ctx, folio->mapping->host, 0);
377+
378+
if (fsverity_add_data_blocks(&ctx, folio, len, offset) &&
379+
fsverity_verify_pending_blocks(&ctx))
380+
return true;
381+
fsverity_clear_pending_blocks(&ctx);
382+
return false;
288383
}
289384
EXPORT_SYMBOL_GPL(fsverity_verify_blocks);
290385

@@ -305,6 +400,8 @@ EXPORT_SYMBOL_GPL(fsverity_verify_blocks);
305400
*/
306401
void fsverity_verify_bio(struct bio *bio)
307402
{
403+
struct inode *inode = bio_first_folio_all(bio)->mapping->host;
404+
struct fsverity_verification_context ctx;
308405
struct folio_iter fi;
309406
unsigned long max_ra_pages = 0;
310407

@@ -321,13 +418,21 @@ void fsverity_verify_bio(struct bio *bio)
321418
max_ra_pages = bio->bi_iter.bi_size >> (PAGE_SHIFT + 2);
322419
}
323420

421+
fsverity_init_verification_context(&ctx, inode, max_ra_pages);
422+
324423
bio_for_each_folio_all(fi, bio) {
325-
if (!verify_data_blocks(fi.folio, fi.length, fi.offset,
326-
max_ra_pages)) {
327-
bio->bi_status = BLK_STS_IOERR;
328-
break;
329-
}
424+
if (!fsverity_add_data_blocks(&ctx, fi.folio, fi.length,
425+
fi.offset))
426+
goto ioerr;
330427
}
428+
429+
if (!fsverity_verify_pending_blocks(&ctx))
430+
goto ioerr;
431+
return;
432+
433+
ioerr:
434+
fsverity_clear_pending_blocks(&ctx);
435+
bio->bi_status = BLK_STS_IOERR;
331436
}
332437
EXPORT_SYMBOL_GPL(fsverity_verify_bio);
333438
#endif /* CONFIG_BLOCK */

0 commit comments

Comments
 (0)