dm-verity
Dm-verity 使用 sha256 哈希树来验证从块设备读取的块。因此,这确保了文件在重启之间或运行时没有被更改。这对于通过减少零日漏洞和未经授权的 root 更改来扩展对操作系统的信任非常有用,以及强制执行安全策略、加密和用户空间安全。Verity 设备是常规的块设备,可以在 /dev/mapper
中访问。
dm-verity 是 Linux 内核中 设备映射器 的一部分,并使用 systemd 实现。
本文主要描述如何设置受 verity 保护的只读根分区。
组件
dm-verity 根设置包含以下内容
- 一个
/
根文件系统镜像或分区, - verity 哈希树
verity.bin
, - verity 树的根哈希
roothash.txt
, systemd-veritysetup.generator
,systemd-veritysetup@.service
,- verity 内核命令行 选项,
- veritysetup (属于 cryptsetup 的一部分),
- 一个 统一内核镜像,其中包含一个 stub EFI 引导加载程序、内核、initramfs、内核命令行和微码:
kernel.efi
, - 安全启动.
推荐使用统一内核镜像和安全启动,但不是必需的。Verity 旨在用作启动过程中保护操作系统和内核免受更改的最后步骤之一。如果没有安全启动和统一内核镜像,它很容易被击败。
准备工作
要启用 dm-verity,您必须已经安装并配置了一个可正常工作的系统。有关详细信息,请参阅 安装指南。
通常,需要一个单独的分区或逻辑卷来存储 verity 哈希数据。
推荐的磁盘布局类似于这样
分区
- 用于引导加载程序的 EFI 系统分区 (
LABEL=ESP
); - XBOOTLDR 分区 (
LABEL=XBOOT
)注意: 使用 systemd 的 XBOOTLDR 分区类型允许您将引导加载程序和内核分开。这对于创建用于安装或更新嵌入式系统和服务器的镜像非常有用,但这要求您使用 systemd-boot。建议也将 Type 1 引导加载程序条目放入此分区。 - 根分区 (
LABEL=OS
,可选择加密,请参阅 静态数据加密)。 - Verity 分区 (
LABEL=VERITY
),应该需要根分区大小的 8-10% - Home (如果您希望用户具有写入权限,则可选)
- Var (如果
/var
不可写,许多程序将无法运行,因此它应该根据用例与根分区分开)
- 注意: 使用文件系统标签而不是 UUID 可以简化在嵌入式设备上部署镜像。两个设备将具有不同的分区 UUID,这使得将 cmdline 捆绑到 UKI 中变得困难。
/home
和 /var
应该是可写的文件系统。在只有一个用途的服务器上,这可能是可选的,因为例如 wireguard 服务器不需要对磁盘的写入访问权限。
mkfs.erofs(1) 为根分区上的 ext4 或 squashfs 提供了一个有吸引力的替代方案。EROFS 与 squashfs 一样,默认情况下不允许写入,并且在许多情况下,在闪存和固态介质上的性能优于类似的文件系统。它默认使用 lz4 压缩,由华为为 Android 手机设计,华为广泛使用 dm-verity。
启动和运行时可能出现的问题
任何需要在 init 期间写入或在运行时更改的文件都必须通过某种方法变为可写,否则程序将无法按预期运行。
许多程序需要对 etc
的写入访问权限。您可以使用单独的 /etc
分区,但这将使所有这些配置文件都可写。创建一个文件夹 /var/etc
并将需要写入访问权限的文件移动到其中,然后符号链接到 etc
,如下面 NetworkManager 的示例所示。
某些程序会期望这些文件夹和文件仍然存在于根文件系统上(即使是只读的),以便进行早期 init。例如,如果 /etc/machine-id
不存在或是符号链接,systemd-journald 将会崩溃。绑定挂载对于此目的可能很有用。
找出系统运行时哪些文件会更改的一种方法是启用 dracut-overlayroot
模块,使用该系统,并检查 /run/overlayroot/u
中的文件,以查看您可能需要处理的内容。此文件夹中的任何文件都被写入到覆盖在根目录之上的 tmpfs 中。将模块放入 /usr/lib/dracut/modules.d/
,将 overlayroot
添加到 dracut 模块列表,并将 overlayroot=1
添加到您的内核命令行,并重新生成 initramfs。该模块可以在 https://github.com/TylerHelt0/dracut-overlayroot[死链 2023-05-06 ⓘ] 找到。
Pacman
由于根文件系统将以只读方式挂载,并且在大多数情况下 /var
应该以读写方式挂载,因此 pacman 数据库的路径应更改为 /usr/lib/pacman
。这将确保 rootfs 始终具有已安装软件包的正确列表。
cp /var/lib/pacman /usr/lib
- 编辑
/etc/pacman.conf
并设置DBPath = /usr/lib/pacman
- 为了能够同步列表和检查更新,请将
/usr/lib/pacman/cache
移动到/var/lib/pacman
并符号链接它。 - 如果您希望能够在不修改根文件系统的情况下更改镜像列表,请将其移动到
/var/etc
并也符号链接它。
NetworkManager
要使用 NetworkManager 设置连接,您需要对 /etc/NetworkManager/system-connections
的写入访问权限。将 system-connections
文件夹移动到 /var/etc/NetworkManager/system-connections
,并在根文件系统上符号链接到它。
# ln -sf /etc/NetworkManager/system-connections /var/etc/NetworkManager/system-connections
设置 verity
- 从 live 介质启动
- 将您的根文件系统以只读方式挂载
- 确保您的所有更改都是完美的
- 执行
veritysetup format root-device verity-device | grep Root | cut -f2 >> roothash.txt
您现在将拥有 rootfs、verity 哈希树和 roothash。或者,您可以通过替换 verity-device
路径将哈希值保存到文件中,并在以后将其写入设备。
要测试它,您可以使用 veritysetup open root-device root verity-device $(cat roothash.txt)
。verity 设备可以从 /dev/mapper/root
挂载。
配置内核命令行
将以下选项添加到您的内核命令行
systemd.verity=1
roothash=contents_of_roothash.txt
systemd.verity_root_data=PATH-TO-ROOT, e.g. LABEL=Root
systemd.verity_root_hash=PATH-TO-VERITY-PARTITION, e.g. LABEL=Verity
systemd.verity_root_options=restart-on-corruption
或panic-on-corruption
(默认行为只会将错误打印到 dmesg,并且不会阻止不受信任的代码运行)
如果 roothash 更改,您还必须编辑 cmdline/使用新值重建统一内核镜像。否则可能会导致系统无法启动。
其他推荐选项
ro
用于防止在不使用 erofs 或 squashfs 的情况下更改 rootrd.emergency=reboot
用于防止在 root 损坏时访问 shellrd.shell=0
用于防止在启动失败时访问 shelllsm=lockdown
启用内核锁定模式,需要签名内核模块lockdown=confidentiality
阻止用户访问内核内存
根目录以外的设备
dm-verity 的使用不限于根设备。需要在启动时验证的其他设备可以放入 /etc/veritytab
中,并将由 systemd-veritysetup@.service
组装。有关更多信息,请参阅 veritytab(5)。
请注意,在系统运行时重新挂载非根分区为 RW 要容易得多。完整性违规也不会触发重启。即使 启用了 verity,具有 root 权限的用户也可以轻松禁用非根分区上的 verity。
安全注意事项
dm-verity 不提供一体化解决方案,而应与其他保护系统的方法一起使用,以防止磁盘被移除和系统完全启动后受到攻击。
安全启动
建议在设置 verity 后启用具有自定义密钥的 安全启动。
如果病毒或攻击者可以替换包含嵌入式 roothash 的 kernel.efi
,这将允许启动任何根文件系统,那么 Verity 保护将毫无用处。为安全启动签名内核镜像将防止内核镜像被替换,并确保根文件系统的完整性,只要固件是安全的。
sbupdate-gitAUR 或 sbctl 可以用于维护您的统一内核镜像并保持您的引导加载程序已签名。sbupdate-gitAUR 也会处理您的内核命令行。sbctl 可用于创建安全启动密钥。
统一内核镜像
UKI 至少将 linux 内核、initramfs、CPU 微码和 cmdline 捆绑在一起。使用 UKI 的优势在于,当 UKI 被签名并与安全启动一起使用时,它可以防止对内核、initramfs 和 cmdline 进行更改。如果 UKI 的 cmdline 部分留空,则可以由像 systemd-boot 这样的引导加载程序提供。否则,只能通过重建和重新签名新的 UKI 来更改它。
如果启用了内核 efistub 或使用了 shim/preloader,则 UEFI 可以直接启动 UKI。
签名内核模块/DKMS
默认内核附带预先签名的本机模块。如果使用 DKMS,则必须创建自定义内核以启用签名和加载 DKMS 模块,当启用安全启动时,这将激活锁定模式。如果您跳过此步骤,DKMS 模块将拒绝加载。
有关签名内核模块的更多信息,请参见此处:签名内核模块
加密
尽管 verity 根设备将是防篡改的,但它不提供机密性。如果它不包含秘密数据,则可以将其放置在未加密的分区上。如果内核受到安全启动的保护,则在不替换内核的情况下,不可能替换根设备或 verity 设备中的数据。
verity 根设备可用于解锁其他加密设备。如果使用密钥文件完成,则 verity 根应该被加密。如果使用 TPM 和 systemd-cryptenroll 存储密钥,则 verity 根可以不加密。
TPM
TPM 2.0 可用于保护包含 root 的 LUKS 设备的加密密钥。启用安全启动后,您可以使用 systemd-cryptenroll
将密钥绑定到 PCR。推荐的 PCR 是 0,1,5,7。如果固件、固件选项、GPT 布局或安全启动状态发生更改,这将分别停止解密。
绑定到 0、1 和 5 的原因是确保攻击者无法替换主板固件以禁用安全启动,从而禁用 verity。
您必须传递此内核选项
rd.luks.options=UUID_of_LUKS=tpm2-device=auto
如果使用 dracut,您可能还需要将 tpm2 支持添加到您的 initramfs 或包含该模块。有关更多信息,请参阅 systemd-cryptenroll#可信平台模块。
systemd-boot
如果您使用 systemd-boot 作为您的引导加载程序,它会将 kernel.efi
测量到 PCR 4 中。这可以用于防止在内核镜像、initramfs 或内核命令行更改时解密 root。
强制访问控制
在运行时,仍然可以使用 OverlayFS、tmpfs 和绑定挂载等方法来获取对 root
中文件夹的写入访问权限。因此,仍然需要加强操作系统的安全性。Apparmor、SELinux 和其他访问控制机制对此很有用。
更新软件包
dm-verity 只读 root 不应以传统的 pacman 方式更新。Verity 主要用于嵌入式设备和其他重视代码完整性而不是滚动发布模型的设备。这具有扩展对操作系统的信任并确保设备始终以相同方式启动的主要好处。例如,用于普通用户的模拟器盒子或安全的 Web 服务器。dm-verity 与 SELinux 等其他安全方法相结合,消除了整个类别的零日漏洞,因此,除非需要新功能,否则更新的需求频率较低。想想运行 Linux 的路由器:许多路由器被恶意软件入侵,用户甚至不知道。如果路由器以安全的方式受到 verity 保护,它将防止病毒获得持久性。
您可以为 /
使用 ext4,这使您可以将其挂载为 rw。然后您可以进行更新,重新哈希文件系统,并在 cmdline 中更改 roothash,但最好是增量发布文件系统的更新镜像。禁用 verity 以在可写文件系统上进行更新就像省略 systemd.verity=1 cmdline 选项一样简单。
构建镜像
虚拟机可用于维护“滚动”系统,并在需要更新时进行镜像。chroot 也可以工作。按照预期在目标系统上工作的方式设置所有分区和启动逻辑,然后将虚拟机重启到 live 介质中并制作分区的镜像。如果您镜像 xboot 分区,则可以直接将其刷入分区以更新 UKI/内核/initramfs/cmdline。如果与根文件系统的镜像配对,这或多或少是一个完整的系统更新。
Systemd 已经具有从更新服务器(或本地目录)检索镜像并将它们刷入可能已经存在或可能不存在的分区的逻辑。它还可以将文件安装和删除到现有分区中。
请参阅 systemd-sysupdate(8) 和 systemd-repart(8)。
A/B 更新方案
处理更新的另一种方法是使用类似于 Android A/B 分区系统的系统。这需要有两组根分区和 verity 分区。当需要更新时,可以将活动分区复制到非活动分区。然后可以像往常一样从 chroot 或使用 pacman 更新非活动分区,并使用 dm-verity “密封”。在下次启动时,非活动分区将变为活动分区。
如果使用 UKI,则必须使用根分区和 verity 分区更新 UKI。至少必须使用新的 roothash 更新内核 cmdline。
overlayfs
如果用户想要一个具有可选持久性或可以安装在重启时还原的软件包的系统,则可以将 overlay 挂载为 root,并将 verity root 作为较低目录。较高目录可以是持久性块设备或 tmpfs。如果使用 A/B,则可以将 /
重新挂载为可写的 OverlayFS 并使用正常的更新方法,然后将 overlayfs 的内容复制到非活动分区并重新哈希 verity。
如果用户需要临时持久性(例如,安装在启动时重置的软件包的能力),则可以在内核命令行上传递 systemd.volatile=overlay
。
Flatpak
Flatpak 可用于在 var
和 home
中安装和更新应用程序,而无需对 /
的写入访问权限。Flatpak 将是解决大多数用户在受 verity 保护的台式 PC 中安装和更新应用程序的需求的理想选择。Flatpak 默认在 /var
上工作。
提示与技巧
自动化
上述步骤可以使用软件包 verity-squash-rootAUR 自动化。它将构建一个 squashfs rootfs 并使用内核和 initramfs 签名 roothash。在启动时,您可以决定启动持久系统,其中 overlayfs 上的更改将被保存,或者启动易失系统。它还会保留最后一个 rootfs 作为备份,因此您可以决定启动最后一个可正常工作的 rootfs。