机密计算虚拟机

Hyper-V 可以创建并运行作为机密计算 (CoCo) 虚拟机的 Linux 客户机。此类虚拟机与物理处理器协作,以更好地保护虚拟机内存中数据的机密性和完整性,即使在管理程序/虚拟机管理器 (VMM) 已被攻破并可能恶意行为的情况下也能实现。Hyper-V 上的 CoCo 虚拟机与Linux x86 虚拟化中的机密计算中描述的通用 CoCo 虚拟机威胁模型和安全目标一致。请注意,Linux 中特定于 Hyper-V 的代码将 CoCo 虚拟机称为“隔离虚拟机”或“隔离式虚拟机”。

Hyper-V 上的 Linux CoCo 虚拟机需要以下组件的协作和交互

  • 带有支持 CoCo 虚拟机的处理器的物理硬件

  • 硬件运行支持 CoCo 虚拟机的 Windows/Hyper-V 版本

  • 虚拟机运行支持作为 CoCo 虚拟机的 Linux 版本

物理硬件要求如下

  • 具有 SEV-SNP 的 AMD 处理器。Hyper-V 不运行使用 AMD SME、SEV 或 SEV-ES 加密的客户机虚拟机,并且此类加密不足以满足 Hyper-V 上的 CoCo 虚拟机要求。

  • 具有 TDX 的 Intel 处理器

要创建 CoCo 虚拟机,在创建虚拟机时必须向 Hyper-V 指定“隔离虚拟机”属性。虚拟机创建后,不能从 CoCo 虚拟机更改为普通虚拟机,反之亦然。

操作模式

Hyper-V CoCo 虚拟机可以运行在两种模式下。模式在创建虚拟机时选择,并且在虚拟机的生命周期内不能更改。

  • 全面感知模式。在此模式下,客户机操作系统经过“感知”以理解和管理作为 CoCo 虚拟机运行的所有方面。

  • 半虚拟化管理程序模式。在此模式下,客户机与主机之间的半虚拟化管理程序层提供作为 CoCo 虚拟机运行所需的一些操作。客户机操作系统可以比全面感知模式所需的 CoCo 感知度更少。

从概念上讲,全面感知模式和半虚拟化管理程序模式可以视为一个光谱上的点,该光谱涵盖了作为 CoCo 虚拟机运行所需的客户机感知程度。全面感知模式是光谱的一端。半虚拟化管理程序模式的完整实现是光谱的另一端,其中作为 CoCo 虚拟机运行的所有方面都由半虚拟化管理程序处理,并且一个对内存加密或 CoCo 虚拟机其他方面一无所知的普通客户机操作系统可以成功运行。然而,Hyper-V 对半虚拟化管理程序模式的实现并未达到如此程度,它处于光谱的中间位置。CoCo 虚拟机的一些方面由 Hyper-V 半虚拟化管理程序处理,而客户机操作系统必须对其他方面进行“感知”。不幸的是,没有对半虚拟化管理程序可能提供的特性/功能进行标准化的枚举,也没有客户机操作系统查询半虚拟化管理程序以获取其提供的特性/功能的标准化机制。对半虚拟化管理程序所提供内容的理解是硬编码在客户机操作系统中的。

半虚拟化管理程序模式与Coconut 项目有相似之处,该项目旨在提供一个受限的半虚拟化管理程序,为客户机提供虚拟 TPM 等服务。然而,Hyper-V 半虚拟化管理程序通常处理比当前为 Coconut 设想的更多 CoCo 虚拟机方面,因此更接近光谱中“无需客户机感知”的一端。

在 CoCo 虚拟机威胁模型中,半虚拟化管理程序位于客户机安全域中,并且必须受到客户机操作系统的信任。这意味着,管理程序/VMM 必须像保护自己免受潜在恶意客户机攻击一样,保护自己免受潜在恶意半虚拟化管理程序攻击。

全面感知模式与半虚拟化管理程序模式的硬件架构方法因底层处理器的不同而异。

  • 对于 AMD SEV-SNP 处理器,在全面感知模式下,客户机操作系统在 VMPL 0 中运行,并完全控制客户机上下文。在半虚拟化管理程序模式下,客户机操作系统在 VMPL 2 中运行,半虚拟化管理程序在 VMPL 0 中运行。在 VMPL 0 中运行的半虚拟化管理程序拥有 VMPL 2 中客户机操作系统所不具备的特权。某些操作需要客户机调用半虚拟化管理程序。此外,在半虚拟化管理程序模式下,客户机操作系统按照 SEV-SNP 架构的定义在“虚拟内存顶部”(vTOM) 模式下运行。当使用半虚拟化管理程序时,此模式简化了客户机对内存加密的管理。

  • 对于 Intel TDX 处理器,在全面感知模式下,客户机操作系统在 L1 虚拟机中运行。在半虚拟化管理程序模式下,使用 TD 分区。半虚拟化管理程序在 L1 虚拟机中运行,而客户机操作系统在嵌套的 L2 虚拟机中运行。

Hyper-V 向客户机公开了一个合成 MSR,用于描述 CoCo 模式。该 MSR 指示底层处理器是否使用 AMD SEV-SNP 或 Intel TDX,以及是否正在使用半虚拟化管理程序。构建一个可以在任一架构和任一模式下正确启动和运行的单一内核映像是直截了当的。

半虚拟化管理程序影响

在半虚拟化管理程序模式下运行会影响通用 Linux 内核 CoCo 虚拟机功能的以下方面

  • 客户机初始内存设置。当在半虚拟化管理程序模式下创建新虚拟机时,半虚拟化管理程序首先运行,并将客户机物理内存设置为加密状态。客户机 Linux 执行正常的内存初始化,但会明确将适当的范围标记为已解密(共享)。在半虚拟化管理程序模式下,Linux 不执行在全面感知模式下使用 AMD SEV-SNP 特别棘手的早期启动内存设置步骤。

  • #VC/#VE 异常处理。在半虚拟化管理程序模式下,Hyper-V 配置客户机 CoCo 虚拟机将 #VC 和 #VE 异常分别路由到 VMPL 0 和 L1 虚拟机,而不是客户机 Linux。因此,这些异常处理程序不在客户机 Linux 中运行,也不是半虚拟化管理程序模式下 Linux 客户机所需的感知功能。

  • CPUID 标志。AMD SEV-SNP 和 Intel TDX 都在客户机中提供一个 CPUID 标志,指示虚拟机正在使用各自的硬件支持。虽然这些 CPUID 标志在全面感知 CoCo 虚拟机中可见,但半虚拟化管理程序会过滤掉这些标志,客户机 Linux 看不到它们。在整个 Linux 内核中,明确测试这些标志的做法已基本被 cc_platform_has() 函数取代,目标是抽象 SEV-SNP 和 TDX 之间的差异。但 cc_platform_has() 抽象也允许 Hyper-V 半虚拟化管理程序配置选择性地启用 CoCo 虚拟机功能的一些方面,即使 CPUID 标志未设置。SEV-SNP 上的早期启动内存设置是个例外,它会测试 CPUID SEV-SNP 标志。但在 Hyper-V 半虚拟化管理程序模式虚拟机中没有该标志,可以实现不运行 SEV-SNP 特定早期启动内存设置的预期效果。

  • 设备仿真。在半虚拟化管理程序模式下,Hyper-V 半虚拟化管理程序提供 IO-APIC 和 TPM 等设备的仿真。由于仿真发生在客户机上下文中的半虚拟化管理程序中(而不是管理程序/VMM 上下文),因此对这些设备的 MMIO 访问必须是加密引用,而不是在全面感知 CoCo 虚拟机中使用的已解密引用。__ioremap_caller() 函数已得到增强,可以进行回调以检查特定地址范围是否应被视为加密(私有)。请参阅“is_private_mmio”回调。

  • 内存加密/解密转换。在 CoCo 虚拟机中,在加密和解密之间转换客户机内存需要与管理程序/VMM 协调。这通过从 __set_memory_enc_pgtable() 调用的回调来实现。在全面感知模式下,使用这些回调的正常 SEV-SNP 和 TDX 实现。在半虚拟化管理程序模式下,使用 Hyper-V 特定的一组回调。这些回调调用半虚拟化管理程序,以便半虚拟化管理程序可以协调转换并根据需要通知管理程序。请参阅 hv_vtom_init() 中这些回调的设置。

  • 中断注入。在全面感知模式下,恶意管理程序可能会在违反 x86/x64 架构规则的时间点向客户机操作系统注入中断。为了提供全面保护,客户机操作系统应包含使用 CoCo 处理器提供的中断注入管理功能的感知功能。在半虚拟化管理程序模式下,半虚拟化管理程序调解向客户机操作系统注入中断,并确保客户机操作系统只看到“合法”中断。半虚拟化管理程序使用 CoCo 物理处理器提供的中断注入管理功能,从而向客户机操作系统隐藏了这些复杂性。

Hyper-V 超级调用

在全面感知模式下,Linux 客户机发出的超级调用会直接路由到管理程序,就像在非 CoCo 虚拟机中一样。但在半虚拟化管理程序模式下,正常的超级调用首先会陷入半虚拟化管理程序,半虚拟化管理程序可能会反过来调用管理程序。但半虚拟化管理程序在这方面有其特殊性,Linux 客户机发出的一些超级调用必须始终直接路由到管理程序。这些超级调用站点会检查半虚拟化管理程序是否存在,并使用特殊的调用序列。例如,请参阅 hv_post_message()。

客户机与 Hyper-V 通信

除了 Linux CoCo 虚拟机中 Linux 内核对内存加密的通用处理之外,Hyper-V 还具有 VMBus 和 VMBus 设备,它们使用 Linux 客户机和主机之间共享的内存进行通信。此共享内存必须标记为已解密才能启用通信。此外,由于威胁模型包括已被攻破和潜在恶意的宿主机,客户机必须提防通过此共享内存将任何非预期数据泄露给宿主机。

这些 Hyper-V 和 VMBus 内存页被标记为已解密

  • VMBus 监控页

  • 合成中断控制器 (synic) 相关页(除非由半虚拟化管理程序提供)

  • 每 CPU 超级调用输入和输出页(除非在半虚拟化管理程序下运行)

  • VMBus 环形缓冲区。直接映射在 __vmbus_establish_gpadl() 中标记为已解密。在 hv_ringbuffer_init() 中创建的二级映射也必须包含“decrypted”属性。

当客户机将数据写入与主机共享的内存时,它必须确保只写入预期数据。在复制到共享内存之前,填充或未使用的字段必须初始化为零,以避免随机内核数据无意中泄露给主机。

同样,当客户机读取与主机共享的内存时,它必须在处理数据之前验证数据,以防止恶意主机诱导客户机暴露非预期数据。进行此类验证可能很棘手,因为主机甚至在验证执行期间或之后都可以修改共享内存区域。对于在 VMBus 环形缓冲区中从主机传递到客户机的消息,会验证消息的长度,并将消息复制到临时(加密)缓冲区中进行进一步验证和处理。复制会增加少量开销,但这是防止恶意主机的唯一方法。请参阅 hv_pkt_iter_first()。

许多 VMBus 设备的驱动程序已通过添加代码来完全验证通过 VMBus 接收到的消息而得到“强化”,而不是假定 Hyper-V 是协同工作的。此类驱动程序在 vmbus_devs[] 表中被标记为“allowed_in_isolated”。其他在 CoCo 虚拟机中不需要的 VMBus 设备驱动程序尚未强化,并且不允许在 CoCo 虚拟机中加载。请参阅 vmbus_is_valid_offer() 中排除此类设备的部分。

两个 VMBus 设备依赖 Hyper-V 主机进行 DMA 数据传输:用于磁盘 I/O 的 storvsc 和用于网络 I/O 的 netvsc。storvsc 使用正常的 Linux 内核 DMA API,因此通过已解密的 swiotlb 内存进行弹跳缓冲是隐式完成的。netvsc 有两种数据传输模式。第一种模式通过由 netvsc 驱动程序显式分配的发送和接收缓冲区空间进行,用于大多数较小的包。这些发送和接收缓冲区由 __vmbus_establish_gpadl() 标记为已解密。由于 netvsc 驱动程序明确地将数据包复制到/从这些缓冲区,因此加密和解密内存之间的弹跳缓冲的等效功能已经成为数据路径的一部分。第二种模式使用正常的 Linux 内核 DMA API,并通过 swiotlb 内存隐式地进行弹跳缓冲,就像在 storvsc 中一样。

最后,VMBus 虚拟 PCI 驱动程序在 CoCo 虚拟机中需要特殊处理。Linux PCI 设备驱动程序使用 Linux PCI 子系统提供的标准 API 访问 PCI 配置空间。在 Hyper-V 上,这些函数直接访问 MMIO 空间,并且访问会陷入 Hyper-V 进行仿真。但在 CoCo 虚拟机中,内存加密阻止 Hyper-V 读取客户机指令流来仿真访问。因此,在 CoCo 虚拟机中,这些函数必须进行超级调用,并显式描述访问的参数。请参阅 _hv_pcifront_read_config() 和 _hv_pcifront_write_config() 以及指示使用超级调用的“use_calls”标志。

load_unaligned_zeropad()

在加密和解密之间转换内存时,set_memory_encrypted() 或 set_memory_decrypted() 的调用者负责确保在转换进行期间内存未在使用且未被引用。转换涉及多个步骤,并包括与 Hyper-V 主机的交互。在所有步骤完成之前,内存处于不一致状态。在此状态不一致时进行引用可能导致无法干净修复的异常。

然而,内核的 load_unaligned_zeropad() 机制可能会产生散乱引用,而 set_memory_encrypted() 或 set_memory_decrypted() 的调用者无法阻止这些引用,因此 #VC 或 #VE 异常处理程序中有专门的代码来修复这种情况。但是,在 Hyper-V 上运行的 CoCo 虚拟机可能被配置为使用半虚拟化管理程序运行,此时 #VC 或 #VE 异常被路由到半虚拟化管理程序。没有架构上的方法可以将异常转发回客户机内核,在这种情况下,#VC/#VE 处理程序中的 load_unaligned_zeropad() 修复代码不会运行。

为避免此问题,Hyper-V 特定功能在转换进行时将页面标记为“不存在”,以通知管理程序转换状态。如果 load_unaligned_zeropad() 导致散乱引用,将生成正常的页错误而不是 #VC 或 #VE,并且基于页错误的 load_unaligned_zeropad() 处理程序会修复该引用。当加密/解密转换完成时,页面再次被标记为“存在”。请参阅 hv_vtom_clear_present() 和 hv_vtom_set_host_visibility()。