dm-verity

Device-Mapper 的“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>]
<版本>

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

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

在散列时附加 salt,连续存储摘要,并且块的其余部分用零填充。

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

在散列时预先添加 salt,每个摘要用零填充到 2 的幂次方。

<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 的加密哈希的十六进制编码。此哈希应被信任,因为除此之外没有其他真实性。

<salt>

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 不兼容。

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>

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

try_verify_in_tasklet

如果校验哈希值在缓存中,则在内核任务中验证数据块,而不是在工作队列中。此选项可以减少 IO 延迟。

操作原理

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