Linux fsverity_file_open fs-verity Merkle树校验
Linux fsverity_file_open fs-verity Merkle树校验
fs-verity是Linux内核的只读文件完整性保护机制,基于Merkle树实现逐块哈希验证。核心入口是fsverity_file_open,在文件打开时验证完整性元数据并初始化验证上下文。
整体框架位于fs/verity/目录,由open.c、verify.c、measure.c、enable.c等文件组成。fsverity_file_open是文件打开路径的钩子,在__fput之前调用:
int fsverity_file_open(struct inode *inode, struct file *file)
{
struct fsverity_info *vi;
int err;
vi = fsverity_get_info(inode);
if (!vi)
return 0;
err = fsverity_check_digest(inode, vi);
if (err)
return err;
file->f_mode |= FMODE_VERITY;
return 0;
}
fsverity_get_info从inode中获取或构造fsverity_info结构体,包含Merkle树的根哈希和测量数据:
struct fsverity_info *fsverity_get_info(const struct inode *inode)
{
struct fsverity_info *vi;
if (inode->i_verity_info)
return inode->i_verity_info;
vi = fsverity_create_info(inode);
if (IS_ERR(vi))
return NULL;
inode->i_verity_info = vi;
return vi;
}
fsverity_create_info解析verity元数据(存储在文件的扩展属性或特殊块中)并构建Merkle树验证上下文:
struct fsverity_info *fsverity_create_info(const struct inode *inode)
{
struct fsverity_info *vi;
struct fsverity_descriptor *desc;
int ret;
vi = kzalloc(sizeof(*vi), GFP_KERNEL);
if (!vi)
return ERR_PTR(-ENOMEM);
desc = kzalloc(sizeof(*desc) + FS_VERITY_MAX_LEVEL_SIZE, GFP_KERNEL);
if (!desc) {
kfree(vi);
return ERR_PTR(-ENOMEM);
}
ret = inode->i_sb->s_vop->get_verity_descriptor(inode, desc, sizeof(*desc));
if (ret < 0) {
kfree(desc);
kfree(vi);
return ERR_PTR(ret);
}
vi->tree_params.hash_algorithm = desc->hash_algorithm;
vi->tree_params.block_size = le32_to_cpu(desc->log_blocksize) ?: PAGE_SIZE;
vi->data_params = fsverity_init_hash_tree_params(desc, 0);
vi->tree_params = fsverity_init_hash_tree_params(desc, FS_VERITY_HASH_TREE);
vi->level_params = fsverity_init_level_params(desc);
memcpy(vi->root_hash, desc->root_hash, desc->hash_algorithm->digest_size);
kfree(desc);
return vi;
}
Merkle树验证发生在每次page fault时的数据读取路径。核心函数是fsverity_verify_blocks,它验证一个或多个数据块:
bool fsverity_verify_blocks(struct inode *inode, struct page **pages, unsigned int count)
{
struct fsverity_info *vi = inode->i_verity_info;
unsigned int level;
int err;
for (unsigned int i = 0; i < count; i++) {
struct page *page = pages[i];
unsigned long offset = page->index << PAGE_SHIFT;
struct ahash_request *req;
SHA256_CTX ctx;
err = fsverity_verify_level(vi, offset, vi->level_params,
&level, vi->root_hash);
if (err)
return false;
req = ahash_request_alloc(vi->tree_params.hash_alg->tfm, GFP_NOFS);
ahash_request_set_crypt(req, NULL, vi->measurement, 0);
crypto_ahash_init(req);
crypto_ahash_update(req, page, PAGE_SIZE);
crypto_ahash_final(req);
if (crypto_memneq(req->result, vi->measurement, vi->digest_size))
return false;
ahash_request_free(req);
}
return true;
}
实际实现中,fsverity_verify_level从叶节点开始逐层向上验证,直到根节点。每个Merkle树节点的哈希验证函数:
static int fsverity_verify_level(struct inode *inode, unsigned long offset,
struct merkle_tree_params *params,
unsigned int *level, const u8 *root_hash)
{
struct fsverity_blockbuf block;
struct fsverity_hash_alg *alg = params->hash_alg;
u8 digest[FS_VERITY_MAX_DIGEST_SIZE];
int err;
for (*level = params->num_levels; *level > 0; (*level)--) {
unsigned long hash_offset = fsverity_hash_level_offset(params, *level, offset);
err = params->hash_blocks[*level - 1]->read(inode, hash_offset, &block);
if (err)
return err;
if (*level == params->num_levels)
fsverity_compute_hash(alg, block.kaddr, block.len, digest);
else
fsverity_compute_hash(alg, block.kaddr, block.len, digest);
if (crypto_memneq(digest, root_hash, alg->digest_size))
return -EBADMSG;
}
return 0;
}
fs-verity的哈希树参数定义:
struct merkle_tree_params {
struct fsverity_hash_alg *hash_alg;
unsigned int block_size;
unsigned int log_blocksize;
unsigned int num_levels;
struct fsverity_blockbuf *hash_blocks;
unsigned int hashes_per_block;
};
Merkle树的层级计算公式:每个数据块计算一个哈希值,多个哈希值组成上一级的哈希块。层级数由文件大小和块大小决定:
static unsigned int fsverity_compute_num_levels(unsigned long data_size,
unsigned int block_size)
{
unsigned int num_levels = 0;
unsigned long blocks = data_size / block_size;
while (blocks > 1) {
blocks = DIV_ROUND_UP(blocks, block_size / sizeof(struct fsverity_hash));
num_levels++;
}
return num_levels;
}
文件打开时的最终校验fsverity_check_digest将文件测量值与用户指定的期望值比较:
static int fsverity_check_digest(const struct inode *inode,
struct fsverity_info *vi)
{
struct fsverity_digest *expected = fsverity_get_digest(inode);
int ret;
if (!expected)
return 0;
if (expected->digest_algorithm != vi->tree_params.hash_algorithm) {
ret = -EINVAL;
goto out;
}
if (crypto_memneq(vi->root_hash, expected->digest,
vi->tree_params.hash_algorithm->digest_size))
ret = -EIO;
else
ret = 0;
out:
kfree(expected);
return ret;
}
fs-verity与IMA(Integrity Measurement Architecture)的区别:fs-verity专注于文件内容的运行时验证,基于Merkle树实现O(1)的增量验证成本,每个读操作只验证访问的数据块路径,而非整个文件。DM-verity类似但工作在块设备层,而fs-verity工作在文件系统层,支持ext4和f2fs。
