LXR | KVM | PM | Time | Interrupt | Systems Performance | Bootup Optimization

device-mapper(2):块设备数据完整性验证功能dm-verity

dm-verity(Device-Mapper Verity)是 Linux 内核中的一个Device-Mapper Target,用于提供块设备数据的完整性验证 功能。它通过哈希树(Hash Tree)和密码学签名,确保设备内容在运行时未被篡改,广泛应用于嵌入式系统、移动设备(如 Android)和关键基础设施中,以防止离线数据篡改攻击。

1 核心原理

(1) 哈希树(Merkle Tree)结构
- 数据分块:将设备数据划分为固定大小的块(如 4KB)。
- 逐层哈希:
- 叶子节点:对每个数据块计算哈希值。
- 中间节点:对子节点哈希值再次哈希,直到生成根哈希(Root Hash)。
- 验证流程:读取数据块时,沿哈希树逐层验证哈希链,确保数据未被篡改。

(2) 根哈希签名
- 根哈希是整个哈希树的最终摘要,代表数据的唯一指纹。
- 根哈希通过私钥签名后存储在安全位置(如独立分区或内核信任的证书中)。
- 内核启动时,使用公钥验证根哈希的签名合法性,确保哈希树可信。

2 主要功能

功能  说明 
 离线数据完整性 检测设备数据在静态存储时的篡改(如恶意修改或硬件故障)。 
 运行时实时验证  在数据访问时动态验证哈希链,确保每次读取的内容未被篡改。
抗回滚攻击   通过版本化的根哈希防止旧版本数据替换(需结合其他机制)。
 只读挂载  验证通过后,设备以只读模式挂载,防止运行时写入破坏完整性。

3 dm-verity target

verity_target是 Linux 内核 Device Mapper 中的一种目标类型,用于实现 dm-verity 功能,主要作用是确保数据的完整性和安全性,防止数据在存储和传输过程中被篡改。通过对数据块和哈希值的验证,确保从存储设备读取的数据与预先计算的哈希值匹配。如果数据被篡改,哈希值验证会失败,从而检测到数据完整性问题。

module_dm(verity)
  dm_register_target--注册verity_target。

 verity_target结构体信息如下:

static struct target_type verity_target = {
        .name           = "verity",
        .features       = DM_TARGET_IMMUTABLE,--表示此类target是不可变的,一旦创建完成其配置和状态就不能再被修改。
        .version        = {1, 9, 0},
        .module         = THIS_MODULE,
        .ctr            = verity_ctr,--构造函数指针,调用此构造函数来初始化target;构造函数负责解析target参数,分配必要资源,并设置target初始状态。
        .dtr            = verity_dtr,--析构函数,调用此函数来释放target所占用的资源。
        .map            = verity_map,--将来自上层IO请求映射到底层物理设备。
        .status         = verity_status,--返回target的状态或进行调试。
        .prepare_ioctl  = verity_prepare_ioctl,--用于确定是否允许对该target执行ioctl操作,并且返回一个底层块设备,ioctl操作将针对该设备执行。
        .iterate_devices = verity_iterate_devices,--编译target所使用的底层设备。
        .io_hints       = verity_io_hints,--提供target的IO性能提示,如IO顺序性、随机性、读写比例等。
};

 verity_ctr解析传入的命令行参数,更新struct dm_target或struct dm_verity参数或根据参数创建资源:

verity_ctr
  verity_fec_ctr_alloc
  dm_table_get_mode
  verity_parse_opt_args--只修改配置,不做实际动作。
  argv[0]--表示struct dm_verity->version。
  dm_get_device--argv[1]和argv[2]分别获取struct dm_verity->data_dev和struct dm_verity->hash_dev对应设备名。
  argv[3]和argv[4]--表示数据块和hash块大小。
  argv[5]--表示数据块数量。
  argv[6]--表示hash值在hash块设备上起始块。
  argv[7]--表示算法名称。
  argv[8]--对应struct dm_verity->root_digest。
  argv[9]--对应struct dm_verity->salt。
  verity_parse_opt_args--跳过前10个参数,解析后面opt参数。
    dm_read_arg_group
    dm_shift_arg
    verity_is_verity_mode/verity_parse_verity_mode--解析ignore_corruption、restart_on_corruption、panic_on_corruption参数,决定在验证失败时采取什么动作:忽略错误,继续运行;重新读取数据,尝试恢复;进入panic,停止执行。
    verity_alloc_zero_digest--判断ignore_zero_blocks是否配置,当启用ignore_zero_blocks时,dm-verity会忽略那些全为零的数据块,不对它们进行哈希验证。
    verity_alloc_most_once
    verity_is_fec_opt_arg/verity_fec_parse_opt_args--解析use_fec_from_device、fec_start、fec_blocks、fec_roots参数。
    verity_verify_is_sig_opt_arg--检查字符串是否是“root_hash_sig_key_desc”。
    verity_verify_sig_parse_opt_args--解析root_hash_sig_key_desc,内核会使用其指向的签名对root哈希值进行验签。
      verity_verify_get_sig_from_key--根据key_desc描述,从文件中找到签名和签名大小信息。
        request_key--key_type_user定义和管理用户空间创建的秘钥,根据key_desc去用户空间查找信息。这个key实际上是签名,通过keyctl padd user加入到内核中。
          request_key_tag
            request_key_and_link
              check_cached_key
              search_process_keyring_rcu
                search_cred_keyrings_rcu
                  keyring_search_rcu
                    search_nested_keyrings
            wait_for_key_construction
        user_key_payload_locked
  verity_verify_root_hash--从root_hash_digest_to_validate获取待验签数据,签名是通过root_hash_sig_key_desc获取。
    verify_pkcs7_signature--有了待验签数据、签名,key为NULL则默认使用builtin_trusted_keys进行验签。
  mempool_init_page_pool
  dm_io_client_create
  dm_bufio_client_create
  dm_bufio_get_device_size
  alloc_workqueue
  verity_fec_ctr
  dm_audit_log_ctr

verity_map将DM对verity target的IO请求映射到底层块设备:

verity_map
  bio_set_dev
  verity_map_sector
  bio_end_sector
  bio_data_dir
  dm_per_bio_data
  verity_fec_init_io
  verity_submit_prefetch
  submit_bio_noacct--函数用于提交一个bio请求到块设备,但不进行I/O统计会计(accounting)。
    bio_endio--函数是一个回调函数,当一个bio请求完成时,内核会调用它。
      bio->bi_end_io--执行传入的bi_end_io()函数,比如verity_end_io。
        verity_end_io
          queue_work--调度执行verity_work。
            verity_work
              verity_verify_io--对io内容进行verify。
                dm_bio_from_per_bio_data
                verity_io_hash_req
                verity_hash_for_block
                  verity_verify_level--从高level开始验证hash,直到level0;然后就用level0的hash对data block进行验证。已验证过的部分会做标记,避免重复验证。
                    verity_hash_at_level--根据level和block(data)计算出,对应的hash_block和hash在block里面的offset。
                    dm_bufio_read--通过bufio读取hash_block数据。
                    dm_bufio_get_aux_data
                    verity_hash
                      verity_hash_init--初始化verity所需的hash运算,包括salt处理。
                      verity_hash_update--根据数据进行hash运算。
                      verity_hash_final--完成计算,并将结果写入digest。
                    memcmp--比对计算得到的hash摘要和预期哈希摘要是否一致。
                verity_hash_init
                verity_for_io_block--对data_block进行hash运算。
                  ahash_request_set_crypt
                  crypto_wait_req
                    crypto_ahash_update
                verity_hash_final--将data_block计算的hash更新。
                memcmp--比较计算的data_block hash摘要和hash_device中读取的预期摘要是否一致。

以一个8GB RAW Data为例,dm-verity依赖的Hash表结构如下:

 

  • 每个Block(4KB)都在hash-tree中记录了对应的hash值。所有Block对应的hash值放在最底层,也就是Level 0。
  • Level 1的hash值由Level 0计算得到。除了Level 0,其它层的hash值都不对应物理上的Block。他们存在的意义是为了构建Hash链,防止Hash篡改。
  • 最高层Block计算得到root-hash,verity参数中需要包含root-hash。
  • 对root-hash进行签名,在verity中指定签名描述,然后创建verity设备时使用内核密钥进行验签。这样就形成完整的验证链条,保证数据安全。

4 veritysetup使用

veritysetup format生成哈希树和元数据,verity创建并映射dm-verity设备,open激活设备,close移除映射,dump输出设备参数,status显示设备运行状态:

veritysetup 2.7.0 flags: UDEV BLKID KEYRING FIPS KERNEL_CAPI HW_OPAL
Usage: veritysetup [OPTION...] <action> <action-specific>

Help options:
  -?, --help                           Show this help message
      --usage                          Display brief usage
  -V, --version                        Print package version
      --cancel-deferred                Cancel a previously set deferred device removal
      --check-at-most-once             Verify data block only the first time it is read
      --data-block-size=bytes          Block size on the data device
      --data-blocks=blocks             The number of blocks in the data file
      --debug                          Show debug messages
      --deferred                       Device removal is deferred until the last user closes it
      --fec-device=path                Path to device with error correction data
      --fec-offset=bytes               Starting offset on the FEC device
      --fec-roots=bytes                FEC parity bytes
      --format=number                  Format type (1 - normal, 0 - original Chrome OS)
  -h, --hash=string                    Hash algorithm
      --hash-block-size=bytes          Block size on the hash device
      --hash-offset=bytes              Starting offset on the hash device
      --ignore-corruption              Ignore corruption, log it only
      --ignore-zero-blocks             Do not verify zeroed blocks
      --no-superblock                  Do not use verity superblock
      --panic-on-corruption            Panic kernel if corruption is detected
      --restart-on-corruption          Restart kernel if corruption is detected
      --root-hash-file=STRING          Path to root hash file
      --root-hash-signature=STRING     Path to root hash signature file
  -s, --salt=hex string                Salt
      --use-tasklets                   Use kernel tasklets for performance
      --uuid=STRING                    UUID for device to use
  -v, --verbose                        Shows more detailed error messages

<action> is one of:
        format <data_device> <hash_device> - format device
        verify <data_device> <hash_device> [<root_hash>] - verify device
        open <data_device> <name> <hash_device> [<root_hash>] - open device as <name>
        close <name> - close device (remove mapping)
        status <name> - show active device status
        dump <hash_device> - show on-disk information

<name> is the device to create under /dev/mapper
<data_device> is the data device
<hash_device> is the device containing verification data
<root_hash> hash of the root node on <hash_device>

Default compiled-in dm-verity parameters:
        Hash: sha256, Data block (bytes): 4096, Hash block (bytes): 4096, Salt size: 32, Hash format: 1

 示例如下:

veritysetup format --data-block-size=4096 --hash-block-size=4096 root.squashfs root.squashfs.verity -s - >root.squashfs.info

root.squashfs作为RAW Data,通过veritysetup生成verity hash数据到root.squashfs.verity,并记录相关信息到root.squashfs.info中。

verity hash数据(root.squashfs.verity)

Verity头struct verity_sb为512B,但是当hash-block-size为4096时,为了对齐,struct verity_sb占用第一个block

下面是一个veritysetup生成hash实例:

  • 所以真正的hash数据在第二个block。
  • root.squashfs需要4096对齐,不足需要补0。否则veritysetup会省去不对齐部分,造成DM设备不完整。

verity信息(root.squashfs.info)

 veritysetup生成的root.squashfs.info信息如下:

VERITY header information for /.../root.squashfs.verity
UUID: 71e7a4e4-d1fb-42a9-a5ec-b12b98f38227
Hash type: 1
Data blocks: 4351
Data block size: 4096
Hash block size: 4096
Hash algorithm: sha256
Salt: -
Root hash: 8d22706af27d0933ffa12c146719d9221ae35e8f8c963cf1ea31864a6b1272d2

组装dm-mod.create参数(bootargs)

对应的dm-mod.create设置如下:

  • 黄色部分是dm-mod.create的通用参数部分。
  • 绿色是dm-verity特有参数。
  • dm-verity还有可选参数。
ubi.mtd=rootfs,2048 ubi.block=0,rootfs root=/dev/dm-0 rootfstype=squashfs ro rootwait
dm-mod.create="rootfs-verity,,,ro,0 34808 verity 1 /dev/ubiblock0_0 /dev/mtdblock8 4096 4096 4351 1 sha256 8d22706af27d0933ffa12c146719d9221ae35e8f8c963cf1ea31864a6b1272d2 -"
dm-mod.waitfor=/dev/ubiblock0_0,/dev/mtdblock8

对dm-mod.create参数解析如下:

  • rootfs-verity,,,ro:按照name,uuid,minor,flags,table格式,表示创建一个名为 “rootfs-verity” 的device-mapper 备;ro表示该 device-mapper 设备是只读的;后面就是table内容。
  • 0:表示该 device-mapper 设备的起始扇区为 0。
  • 34808:表示该 device-mapper 设备的扇区数量为34808。这个数字的来源是veritysetup时Data blocks数量,由于Data block size为4096,sector大小为512,所以需要(Data blocks)*8
  • verity:指定使用的 target 类型为 verity。
  • 1:struct dm_verity->version版本号。
  • /dev/ubiblock0_0 /dev/mtdblock8:表示数据块数据设备为 /dev/ubiblock0_0,对应root.squashfs数据;hash数据设备为/dev/mtdblock8,对应root.squashfs.verity数据
  • 4096 4096:表示数据块大小和哈希块大小均为 4096 位。
  • 4351:表示数据块数量。
  • 1:表示哈希值在哈希块设备上的起始块。作为一个单独的hash设备,不是从0开始,因为veritty_sb头还占用一个block。
  • sha256:表示使用的哈希算法为 sha256。
  • 8d22706af27d0933ffa12c146719d9221ae35e8f8c963cf1ea31864a6b1272d2:表示根哈希值。
  • -:用于增强哈希安全性的盐值。-表示不使用。

dm-verity的可选参数格式为:<可选参数数量> <空格区隔的可选参数列表>。

  • 检测到坏块时行为,3种选项互斥:
    • ignore_corruption:允许读取损坏块并记录日志,不中断操作。
    • restart_on_corruption:检测到损坏时触发系统重启,需用户空间支持。
    • panic_on_corruption:发现损坏时触发内核恐慌,强制停机以保证安全。
  • ignore_zero_blocks:跳过零值块的验证,直接返回零以减少计算开销。
  • FEC(Forward Error Correction)是一种通过在数据中添加冗余信息来检测和纠正传输或存储过程中数据错误的技术。
    • use_fec_from_device <fec_dev>:指定FEC设备,通过冗余数据恢复损坏块。
    • fec_roots <num>:定义FEC冗余数据量(如RS编码的根数),决定纠错能力。
    • fec_blocks <num>:指定FEC设备上的编码数据块总数,需匹配存储容量。
    • fec_start <offset>:设置FEC数据的起始偏移(按数据块大小计算)。
  • check_at_most_once:数据块仅首次验证哈希,牺牲实时安全性以提升性能。
  • root_hash_sig_key_desc <key_description>:指定验证根哈希签名的密钥描述符,增强启动链安全性。
  • try_verify_in_tasklet:在任务软中断中验证缓存哈希,降低I/O延迟。

 

posted on 2025-05-24 23:59  ArnoldLu  阅读(353)  评论(0)    收藏  举报

导航