机密虚拟机中LUKS2磁盘加密漏洞深度剖析

本文详细披露了在八个不同机密计算系统中发现的Linux统一密钥设置版本2(LUKS2)磁盘加密漏洞。攻击者利用这些漏洞可提取磁盘上的所有机密数据并任意修改其内容。文章深入探讨了漏洞成因、利用方式、披露时间线及修复方案。

机密虚拟机中LUKS2磁盘加密漏洞

Trail of Bits 披露了八个使用 Linux 统一密钥设置版本 2(LUKS2)进行磁盘加密的机密计算系统中的漏洞。利用这些漏洞,能够访问存储磁盘的恶意行为者可以提取存储在该磁盘上的所有机密数据,并且可以任意修改磁盘内容。这些漏洞是由可塑的元数据头引起的,攻击者可以利用此漏洞诱骗可信执行环境客户使用空密码加密秘密数据。

以下 CVE 与此披露相关:

  • CVE-2025-59054
  • CVE-2025-58356

这是一次协调披露;我们已通知以下项目,他们在我们发布之前修复了这些问题。

  • Oasis Protocol: oasis-sdk (v0.7.2)
  • Phala Network: dstack (v0.5.4)
  • Flashbots TDX: tdx-init (v0.2.0)
  • Secret Network: secret-vm-ops
  • Fortanix Salmiac: salmiac
  • Edgeless Constellation: constellation (v2.24.0)
  • Edgeless Contrast: contrast (v1.12.1, v1.13.0)
  • Cosmian VM: cosmian-vm

我们通知了 cryptsetup 的维护者,这促成了 cryptsetup v2.8.1 中引入的部分缓解措施。 我们还通知了 Confidential Containers 项目,他们表示相关代码(属于 guest-components 存储库的一部分)目前未在生产中使用。

使用这些机密计算框架的用户应更新到最新版本。远程认证报告的消费者应在认证报告中禁止使用修补前的版本。

利用此问题需要写访问加密磁盘的权限。我们没有发现任何迹象表明此问题已在野外被利用。

这些系统都使用可信执行环境(如 AMD SEV-SNP 和 Intel TDX)来保护机密 Linux 虚拟机免受潜在恶意主机的侵害。每个系统都依赖 LUKS2 来保护用于保存虚拟机持久状态的磁盘卷。LUKS2 是一种磁盘加密格式,最初设计用于 PC 和服务器硬盘的静态加密。我们发现,在磁盘可能受到攻击者修改的场景中,LUKS 并不总是安全的。

机密虚拟机

受影响的系统是基于 Linux 的机密虚拟机(CVMs)。这些不是具有用户登录功能的交互式 Linux 机器;它们是专门的自动化系统,旨在在不信任的环境中运行时处理秘密。典型的用例是私有 AI 推理、私有区块链或多方数据协作。这样的系统应满足以下要求:

  1. 机密性:主机操作系统应无法读取 CVM 内的内存或数据。
  2. 完整性:主机操作系统应无法干扰 CVM 的逻辑操作。
  3. 真实性:远程方应能够验证他们正在与运行预期程序的真实 CVM 进行交互。

远程用户通过远程认证过程验证 CVM 的真实性,在此过程中,安全硬件会生成一个由硬件制造商提供的秘密密钥签名的“引用”。此引用包含 CVM 配置和代码的测量值。如果能够访问主机机器的攻击者可以从 CVM 读取秘密数据或篡改其运行的代码,则系统的安全保证就被破坏了。

机密计算场景颠覆了典型的信任假设。几十年的工作致力于保护主机免受恶意虚拟机的侵害,但很少有 Linux 实用程序旨在保护虚拟机免受恶意主机的侵害。本文描述的问题只是基于 CVM 的系统必须导航的、更广泛的不安全模式雷区中的一个陷阱。如果你的团队正在构建机密计算解决方案,并担心未知的隐患,我们很乐意安排一次免费的办公时间电话,与我们的工程师之一进行交流。

LUKS2 磁盘格式

使用 LUKS2 加密格式的磁盘以头开始,后面跟着实际的加密数据。头包含两个相同的二进制和 JSON 格式元数据部分的副本,然后是一些数量的密钥槽。

图 1:LUKS2 磁盘加密格式

每个密钥槽包含卷密钥的一个副本,使用单个用户密码或令牌进行加密。JSON 元数据部分定义了哪些密钥槽已启用、用于解锁每个密钥槽的密码以及用于加密数据段的密码。

下面是一个具有单个密钥槽的磁盘的典型 JSON 元数据对象。密钥槽使用 Argon2id 和 AES-XTS 在用户密码下加密卷密钥。段对象定义了用于加密数据卷的密码。摘要对象存储卷密钥的哈希值,cryptsetup 使用它来检查是否提供了正确的密码。

图 2:具有单个密钥槽的磁盘的示例 JSON 元数据对象

LUKS, ma—No keys

默认情况下,LUKS2 使用 AES-XTS 加密,这是一种保持大小的标准加密模式。还可能支持哪些其他模式?截至 cryptsetup 版本 2.8.0,以下头将被接受。

图 3:加密设置为 cipher_null-ecb 的可接受头

cipher_null-ecb 算法什么也不做。它忽略其密钥并原样返回数据。特别是,它简单地忽略其密钥,并对数据执行恒等函数。任何攻击者都可以更改密码,调整一些摘要,然后将生成的磁盘交给一个不知情的 CVM;随后,CVM 将像安全加密一样使用该磁盘,从完全未加密的卷中读取配置数据并向其中写入秘密。

当空密码用于加密密钥槽时,该密钥槽可以用任何密码成功打开。在这种情况下,攻击者不需要有关 CVM 加密密钥的任何信息即可生成恶意磁盘。

我们将此问题披露给了 cryptsetup 维护者,他们警告说 LUKS 并非旨在提供此场景下的完整性,并断言空密码的存在对于向后兼容性很重要。在 cryptsetup 2.8.1 及更高版本中,当与非空密码一起使用时,空密码现在作为密钥槽密码被拒绝。

空密码在 cryptsetup 2.8.1 中仍然作为卷密钥的有效选项存在。为了利用此弱点,攻击者只需观察使用目标 CVM 密码格式化的某个加密磁盘的头。当卷加密设置为 cipher_null-ecb 且密钥槽密码保持不变时,CVM 将能够使用其密码解锁密钥槽并开始使用未加密的卷而不会出错。

验证 LUKS 元数据

对于任何机密计算应用,在使用前完全验证 LUKS 头至关重要。幸运的是,cryptsetup 提供了一种分离头模式,允许从 tmpfs 文件而不是不受信任的磁盘读取磁盘头,如下例所示:

1
cryptsetup open --header /tmp/luks_header /dev/vdb

在所有修复方案中,使用分离头模式至关重要,以防止检查时间到使用时间(TOCTOU)攻击。

除了空密码问题之外,LUKS 元数据处理是一个复杂且潜在危险的过程。例如,CVE-2021-4122 曾利用类似问题在自动恢复过程中静默解密整个磁盘。

一旦头驻留在受保护的内存中,有三种潜在的方法可以验证头:

  1. 使用 MAC 确保头在初始创建后未被修改。
  2. 验证头参数以确保仅使用安全值。
  3. 将头作为测量值包含在 TPM 或远程 KMS 认证中。

我们建议尽可能采用第一种方案;通过对整个头计算 MAC,应用程序可以确保头完全未被恶意行为者修改。请参阅 Flashbots 在 tdx-init 中对此修复的实现,作为该技术的一个示例。

如果需要向后兼容性,应用程序可以解析 JSON 元数据部分并验证所有相关字段,如下例所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#!/bin/bash
set -e
# 将头存储在机密 RAM 文件系统中
cryptsetup luksHeaderBackup --header-backup-file /tmp/luks_header $BLOCK_DEVICE;
# 将 JSON 元数据头转储到文件
cryptsetup luksDump --type luks2 --dump-json-metadata /tmp/luks_header > header.json
# 验证头
python validate.py header.json
# 使用 key.txt 打开 cryptfs
cryptsetup open --type luks2 --header /tmp/luks_header $BLOCK_DEVICE --key-file=key.txt

以下是一个示例验证脚本:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from json import load
import sys

with open(sys.argv[1], "r") as f:
    header = load(f)

if len(header["keyslots"]) != 1:
    raise ValueError("Expected 1 keyslot")

if header["keyslots"]["0"]["type"] != "luks2":
    raise ValueError("Expected luks2 keyslot")

if header["keyslots"]["0"]["area"]["encryption"] != "aes-xts-plain64":
    raise ValueError("Expected aes-xts-plain64 encryption")

if header["keyslots"]["0"]["kdf"]["type"] != "argon2id":
    raise ValueError("Expected argon2id kdf")

if len(header["tokens"]) != 0:
    raise ValueError("Expected 0 tokens")

if len(header["segments"]) != 1:
    raise ValueError("Expected 1 segment")
if header["segments"]["0"]["type"] != "crypt":
    raise ValueError("Expected crypt segment")

if header["segments"]["0"]["encryption"] != "aes-xts-plain64":
    raise ValueError("Expected aes-xts-plain64 encryption")

if "flags" in header["segments"]["0"] and header["segments"]["0"]["flags"]:
    raise ValueError("Segment contains unexpected flags")

最后,可以将头数据(去除任何随机盐和摘要)测量到认证状态中。此测量值被纳入任何 TPM 密封 PCR 或发送到 KMS 的认证中。在此模型中,LUKS 头配置成为 CVM 身份的一部分,并允许远程验证者就允许哪些配置接收解密密钥设置任意策略。

协调披露

披露按以下时间线发送:

  • 2025年10月8日:在一次安全审查中发现此模式的一个实例
  • 2025年10月12日:披露给 Cosmian VM
  • 2025年10月14日:披露给 Flashbots
  • 2025年10月15日:披露给上游 cryptsetup (#954)
  • 2025年10月15日:通过 Immunefi 披露给 Oasis Protocol
  • 2025年10月18日:披露给 Edgeless、Dstack、Confidential Containers、Fortanix 和 Secret Network
  • 2025年10月19日:在 cryptsetup 2.8.1 中发布禁用密钥槽中 cipher_null 的部分补丁

截至 2025 年 10 月 30 日,我们了解到针对这些披露的以下补丁:

  • Flashbots tdx-init 使用基于 MAC 的验证进行了修补。
  • Edgeless Constellation 使用头 JSON 验证进行了修补。
  • Oasis ROFL 使用头 JSON 验证进行了修补。
  • Dstack 使用头 JSON 验证进行了修补。
  • Fortanix Salmiac 使用基于 MAC 的验证进行了修补。
  • Cosmian VM 使用头 JSON 验证进行了修补。
  • Secret Network 使用头 JSON 验证进行了修补。

Confidential Containers 团队指出,持久存储功能仍在开发中,反馈将在实现成熟时纳入。

我们要感谢 Oasis Network 通过 Immunefi 为此披露提供了漏洞赏金。感谢 Applied Blockchain、Flashbots、Edgeless Systems、Dstack、Fortanix、Confidential Containers、Cosmian 和 Secret Network 就此披露与我们协调。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计