]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
fsverity: generate and store zero-block hash
authorAndrey Albershteyn <aalbersh@kernel.org>
Wed, 20 May 2026 12:37:02 +0000 (14:37 +0200)
committerChristian Brauner <brauner@kernel.org>
Fri, 5 Jun 2026 11:36:22 +0000 (13:36 +0200)
Compute the hash of one filesystem block's worth of zeros. A filesystem
implementation can decide to elide merkle tree blocks containing only
this hash and synthesize the contents at read time.

Let's pretend that there's a file containing 131 data block and whose
merkle tree looks roughly like this:

root
 +--leaf0
 |   +--data0
 |   +--data1
 |   +--...
 |   `--data128
 `--leaf1
     +--data129
     +--data130
     `--data131

If data[0-128] are sparse holes, then leaf0 will contain a repeating
sequence of @zero_digest.  Therefore, leaf0 need not be written to disk
because its contents can be synthesized.

A subsequent xfs patch will use this to reduce the size of the merkle
tree when dealing with sparse gold master disk images and the like.

Note that this works only on the first-level (data holes). fsverity
doesn't store/generate zero_digest for any higher levels.

Add a helper to pre-fill folio with hashes of empty blocks. This will be
used by iomap to synthesize blocks full of zero hashes on the fly.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Acked-by: Eric Biggers <ebiggers@kernel.org>
Signed-off-by: Andrey Albershteyn <aalbersh@kernel.org>
Link: https://patch.msgid.link/20260520123722.405752-5-aalbersh@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
fs/verity/fsverity_private.h
fs/verity/measure.c
fs/verity/open.c
fs/verity/pagecache.c
include/linux/fsverity.h

index 6e6854c19078de0eec08f88565b918c2f61a3ce7..881d46f25e087c4051011a99fb5efd51184c5c20 100644 (file)
@@ -53,6 +53,9 @@ struct merkle_tree_params {
        u64 tree_size;                  /* Merkle tree size in bytes */
        unsigned long tree_pages;       /* Merkle tree size in pages */
 
+       /* the hash of an all-zeroes block */
+       u8 zero_digest[FS_VERITY_MAX_DIGEST_SIZE];
+
        /*
         * Starting block index for each tree level, ordered from leaf level (0)
         * to root level ('num_levels - 1')
index 6a35623ebdf09d281747183cb7d68320b928c300..818083507885a5a815755a709120aa3646c4d9a4 100644 (file)
@@ -68,8 +68,8 @@ EXPORT_SYMBOL_GPL(fsverity_ioctl_measure);
  * @alg: (out) the digest's algorithm, as a FS_VERITY_HASH_ALG_* value
  * @halg: (out) the digest's algorithm, as a HASH_ALGO_* value
  *
- * Retrieves the fsverity digest of the given file.  The file must have been
- * opened at least once since the inode was last loaded into the inode cache;
+ * Retrieves the fsverity digest of the given file. The
+ * fsverity_ensure_verity_info() must be called on the inode beforehand;
  * otherwise this function will not recognize when fsverity is enabled.
  *
  * The file's fsverity digest consists of @raw_digest in combination with either
index dfa0d1afe0feb3039845b6f4b4de0eb75a472d1d..d0c56a7faa3bb10f99daf40bd812ad239117ea7e 100644 (file)
@@ -153,6 +153,9 @@ int fsverity_init_merkle_tree_params(struct merkle_tree_params *params,
                goto out_err;
        }
 
+       fsverity_hash_block(params, page_address(ZERO_PAGE(0)),
+                           params->zero_digest);
+
        params->tree_size = offset << log_blocksize;
        params->tree_pages = PAGE_ALIGN(params->tree_size) >> PAGE_SHIFT;
        return 0;
index 1819314ecaa35e351d2a01b9e7b34151ac980135..99f5f53eea985ff3dc7bad0e08bee0661d507b6a 100644 (file)
@@ -2,6 +2,7 @@
 /*
  * Copyright 2019 Google LLC
  */
+#include "fsverity_private.h"
 
 #include <linux/export.h>
 #include <linux/fsverity.h>
@@ -56,3 +57,24 @@ void generic_readahead_merkle_tree(struct inode *inode, pgoff_t index,
                folio_put(folio);
 }
 EXPORT_SYMBOL_GPL(generic_readahead_merkle_tree);
+
+/**
+ * fsverity_fill_zerohash() - fill folio with hashes of zero data block
+ * @folio:     folio to fill
+ * @offset:    offset in the folio to start
+ * @len:       length of the range to fill with hashes
+ * @vi:                fsverity info
+ */
+void fsverity_fill_zerohash(struct folio *folio, size_t offset, size_t len,
+                             struct fsverity_info *vi)
+{
+       size_t off = offset;
+
+       WARN_ON_ONCE(!IS_ALIGNED(offset, vi->tree_params.digest_size));
+       WARN_ON_ONCE(!IS_ALIGNED(len, vi->tree_params.digest_size));
+
+       for (; off < (offset + len); off += vi->tree_params.digest_size)
+               memcpy_to_folio(folio, off, vi->tree_params.zero_digest,
+                               vi->tree_params.digest_size);
+}
+EXPORT_SYMBOL_GPL(fsverity_fill_zerohash);
index a8f9aa75b7922f2919ad71e31cf316190c2dbada..6c467ded9751fc3980d208c102372623ae28c6f8 100644 (file)
@@ -201,6 +201,8 @@ bool fsverity_verify_blocks(struct fsverity_info *vi, struct folio *folio,
                            size_t len, size_t offset);
 void fsverity_verify_bio(struct fsverity_info *vi, struct bio *bio);
 void fsverity_enqueue_verify_work(struct work_struct *work);
+void fsverity_fill_zerohash(struct folio *folio, size_t offset, size_t len,
+                           struct fsverity_info *vi);
 
 #else /* !CONFIG_FS_VERITY */
 
@@ -281,6 +283,12 @@ static inline void fsverity_enqueue_verify_work(struct work_struct *work)
        WARN_ON_ONCE(1);
 }
 
+static inline void fsverity_fill_zerohash(struct folio *folio, size_t offset,
+               size_t len, struct fsverity_info *vi)
+{
+       WARN_ON_ONCE(1);
+}
+
 #endif /* !CONFIG_FS_VERITY */
 
 static inline bool fsverity_verify_folio(struct fsverity_info *vi,