dm-verity

设备映射器的“verity”目标通过使用内核加密 API 提供的加密摘要,为块设备提供透明的完整性检查。此目标是只读的。

构建参数

<version> <dev> <hash_dev>
<data_block_size> <hash_block_size>
<num_data_blocks> <hash_start_block>
<algorithm> <digest> <salt>
[<#opt_params> <opt_params>]
<version>

这是磁盘哈希格式的类型。

0 是 Chromium OS 中使用的原始格式。

哈希时附加盐值,摘要连续存储,块的其余部分用零填充。

1 是应在新设备上使用的当前格式。

哈希时预置盐值,每个摘要用零填充到二的幂次。

<dev>

这是包含需要检查完整性的数据的设备。可以指定为路径,如 /dev/sdaX,或设备号,<major>:<minor>。

<hash_dev>

这是提供哈希树数据的设备。可以类似于设备路径指定,也可以是同一设备。如果使用同一设备,则 hash_start 应位于配置的 dm-verity 设备之外。

<data_block_size>

数据设备上的块大小,以字节为单位。每个块对应于哈希设备上的一个摘要。

<hash_block_size>

哈希块的大小,以字节为单位。

<num_data_blocks>

数据设备上的数据块数量。附加块不可访问。您可以将哈希值放在与数据相同的分区上,在这种情况下,哈希值放置在 <num_data_blocks> 之后。

<hash_start_block>

这是从 hash_dev 的开头到哈希树的根块的偏移量,以 <hash_block_size> 块为单位。

<algorithm>

用于此设备的加密哈希算法。这应该是算法的名称,如“sha1”。

<digest>

根哈希块和盐值的加密哈希的十六进制编码。此哈希应被信任,因为在此之后没有其他认证。

<salt>

盐值的十六进制编码。

<#opt_params>

可选参数的数量。如果没有可选参数,可以跳过可选参数部分,或者 #opt_params 可以为零。否则 #opt_params 是后面参数的数量。

可选参数部分的示例

1 ignore_corruption

ignore_corruption

记录损坏的块,但允许读取操作正常进行。

restart_on_corruption

发现损坏的块时重启系统。此选项与 ignore_corruption 不兼容,并且需要用户空间支持以避免重启循环。

panic_on_corruption

发现损坏的块时使设备崩溃。此选项与 ignore_corruption 和 restart_on_corruption 不兼容。

restart_on_error

检测到 I/O 错误时重启系统。此选项可以与 restart_on_corruption 选项结合使用。

panic_on_error

检测到 I/O 错误时使设备崩溃。此选项与 restart_on_error 选项不兼容,但可以与 panic_on_corruption 选项结合使用。

ignore_zero_blocks

不验证预期包含零的块,并始终返回零。如果分区包含未使用的块且不保证包含零,这可能很有用。

use_fec_from_device <fec_dev>

如果哈希验证失败,使用前向纠错(FEC)从损坏中恢复。使用指定设备的编码数据。这可能是数据和哈希块所在的同一设备,在这种情况下,fec_start 必须位于数据和哈希区域之外。

如果编码数据包含附加元数据,则必须在哈希块之后在哈希设备上可访问。

注意:数据和哈希设备的块大小必须匹配。此外,如果 verity <dev> 已加密,则 <fec_dev> 也应加密。

fec_roots <num>

生成器根的数量。这等于编码数据中奇偶校验字节的数量。例如,在 RS(M, N) 编码中,根的数量是 M-N。

fec_blocks <num>

FEC 设备上编码数据块的数量。FEC 设备的块大小是 <data_block_size>。

fec_start <offset>

这是从 FEC 设备开始到编码数据开始的偏移量,以 <data_block_size> 块为单位。

check_at_most_once

仅在数据块第一次从数据设备读取时进行验证,而不是每次读取时都验证。这减少了 dm-verity 的开销,使其可以在内存和/或 CPU 受限的系统上使用。但是,它提供的安全性级别较低,因为只会检测数据设备内容的离线篡改,而不是在线篡改。

哈希块每次从哈希设备读取时仍会被验证,因为哈希块的验证不如数据块的性能关键,并且在它覆盖的所有数据块都被验证之后,哈希块将不再被验证。

root_hash_sig_key_desc <key_description>

这是 USER_KEY 的描述,内核将查找该密钥以获取根哈希的 pkcs7 签名。pkcs7 签名用于在创建设备映射器块设备期间验证根哈希。根哈希的验证取决于内核中是否设置了 config DM_VERITY_VERIFY_ROOTHASH_SIG。默认情况下,签名会根据内置的可信密钥环进行检查,如果设置了 DM_VERITY_VERIFY_ROOTHASH_SIG_SECONDARY_KEYRING,则会根据辅助可信密钥环进行检查。辅助可信密钥环默认包含内置的可信密钥环,如果它们由辅助可信密钥环中已有的证书签名,则它也可以在运行时获得新证书。

try_verify_in_tasklet

如果 verity 哈希在缓存中且 I/O 大小不超过限制,则在下半部分而不是工作队列中验证数据块。此选项可以减少 I/O 延迟。大小限制可以通过 /sys/module/dm_verity/parameters/use_bh_bytes 配置。这四个参数依次对应 IOPRIO_CLASS_NONE、IOPRIO_CLASS_RT、IOPRIO_CLASS_BE 和 IOPRIO_CLASS_IDLE 的限制。例如:<none>,<rt>,<be>,<idle> 4096,4096,4096,4096

操作理论

dm-verity 旨在作为经过验证的引导路径的一部分进行设置。这可以是任何操作,从使用 tboot 或 trustedgrub 引导到仅从已知良好设备(如 USB 驱动器或 CD)引导。

配置 dm-verity 设备时,调用者应已通过某种方式(加密签名等)进行了身份验证。实例化后,所有哈希将在磁盘访问期间按需验证。如果它们无法验证到树的根节点(即根哈希),则 I/O 将失败。这应该能检测到设备上任何数据和哈希数据的篡改。

加密哈希用于按块断言设备的完整性。这允许在第一次读取到页缓存时进行轻量级哈希计算。块哈希线性存储,并对齐到最接近的块大小。

如果启用了前向纠错(FEC)支持,任何损坏数据的恢复都将使用相应数据的加密哈希进行验证。这就是为什么将纠错与完整性检查结合起来至关重要。

哈希树

树中的每个节点都是一个加密哈希。如果它是叶节点,则计算磁盘上某个数据块的哈希。如果它是中间节点,则计算多个子节点的哈希。

树中的每个条目都是一组相邻节点,它们适合一个块。数量根据 block_size 和所选加密摘要算法的大小确定。哈希值在此条目中线性排序,任何未对齐的尾随空间都将被忽略,但在计算父节点时会包含在内。

树看起来像这样

alg = sha256, num_blocks = 32768, block_size = 4096

                            [   root    ]
                           /    . . .    \
                [entry_0]                 [entry_1]
               /  . . .  \                 . . .   \
    [entry_0_0]   . . .  [entry_0_127]    . . . .  [entry_1_127]
      / ... \             /   . . .  \             /           \
blk_0 ... blk_127  blk_16256   blk_16383      blk_32640 . . . blk_32767

磁盘格式

verity 内核代码不读取磁盘上的 verity 元数据头。它只读取紧随其后的哈希块。用户空间工具应验证 verity 头的完整性。

或者,可以省略头,并通过内核命令行传递 dmsetup 参数,在受信任的链中验证命令行。

紧随头之后(并按扇区号填充到下一个哈希块边界)是哈希块,它们按深度存储(从根开始),并按索引递增的顺序排序。

内核参数和磁盘元数据格式的完整规范可在 cryptsetup 项目的 wiki 页面上找到

状态

如果到目前为止执行的每个检查都有效,则返回 V(表示有效)。如果任何检查失败,则返回 C(表示损坏)。

示例

设置设备

# dmsetup create vroot --readonly --table \
  "0 2097152 verity 1 /dev/sda1 /dev/sda2 4096 4096 262144 1 sha256 "\
  "4392712ba01368efdf14b05c76f9e4df0d53664630b5d48632ed17a137f39076 "\
  "1234000000000000000000000000000000000000000000000000000000000000"

有一个命令行工具 veritysetup 可用于计算或验证哈希树或激活内核设备。这可从 cryptsetup 上游仓库 https://gitlab.com/cryptsetup/cryptsetup/ 获取(作为 libcryptsetup 扩展)。

在设备上创建哈希

# veritysetup format /dev/sda1 /dev/sda2
...
Root hash: 4392712ba01368efdf14b05c76f9e4df0d53664630b5d48632ed17a137f39076

激活设备

# veritysetup create vroot /dev/sda1 /dev/sda2 \
  4392712ba01368efdf14b05c76f9e4df0d53664630b5d48632ed17a137f39076