19. AMD 内存加密

安全内存加密(SME)和安全加密虚拟化(SEV)是 AMD 处理器上提供的功能。

SME 提供了一种能力,即使用标准的 x86 页表将单个内存页标记为已加密。被标记为加密的页在从 DRAM 读取时会自动解密,在写入 DRAM 时会自动加密。因此,SME 可用于保护 DRAM 内容免受针对系统的物理攻击。

SEV 允许运行加密的虚拟机 (VM),其中来宾虚拟机(guest VM)的代码和数据受到保护,从而使解密后的版本仅在虚拟机内部可用。SEV 来宾虚拟机具有私有内存和共享内存的概念。私有内存使用虚拟机专用的密钥进行加密,而共享内存可以使用虚拟机监控程序(hypervisor)的密钥进行加密。启用 SME 时,hypervisor 的密钥与 SME 中使用的密钥相同。

当页表项设置了加密位时,该页即被加密(有关如何确定其位置,请参见下文)。加密位也可以在 cr3 寄存器中指定,从而允许对 PGD 表进行加密。通过在指向下一个表的页表项中设置加密位,可以加密后续各级别的页表。这允许加密整个页表层级结构。注意,这意味着仅仅在 cr3 中设置了加密位,并不意味着整个层级结构都被加密。要做到这一点,层级结构中的每个页表项都需要设置加密位。因此,理论上,你可以在 cr3 中设置加密位以加密 PGD,但不在 PUD 的 PGD 条目中设置加密位,这会导致该条目指向的 PUD 不被加密。

启用 SEV 时,指令页和来宾页表始终被视为私有的。来宾内部的所有 DMA 操作必须在共享内存上执行。由于当来宾操作系统在 64 位或 32 位 PAE 模式下运行时,内存加密位由其控制,因此在所有其他模式下,SEV 硬件会强制将内存加密位设为 1。

对 SME 和 SEV 的支持可以通过 CPUID 指令来确定。CPUID 函数 0x8000001f 报告与 SME 相关的信息

0x8000001f[eax]:
        Bit[0] indicates support for SME
        Bit[1] indicates support for SEV
0x8000001f[ebx]:
        Bits[5:0]  pagetable bit number used to activate memory
                   encryption
        Bits[11:6] reduction in physical address space, in bits, when
                   memory encryption is enabled (this only affects
                   system physical addresses, not guest physical
                   addresses)

如果支持 SME,可以使用 MSR 0xc00100010 (MSR_AMD64_SYSCFG) 来确定是否启用了 SME 和/或启用内存加密

0xc0010010:
        Bit[23]   0 = memory encryption features are disabled
                  1 = memory encryption features are enabled

如果支持 SEV,可以使用 MSR 0xc0010131 (MSR_AMD64_SEV) 来确定 SEV 是否处于活动状态

0xc0010131:
        Bit[0]    0 = memory encryption is not active
                  1 = memory encryption is active

Linux 依赖 BIOS 来设置此位(如果 BIOS 确定启用内存加密导致的物理地址空间减少(见上面的 CPUID 信息)不会与系统的地址空间资源需求冲突)。如果 Linux 启动时未设置此位,则 Linux 自身不会设置它,并且内存加密将无法实现。

Linux 内核中 SME 的状态记录如下

  • Supported(支持):CPU 支持 SME(通过 CPUID 指令确定)。

  • Enabled(已启用):支持且设置了 MSR_AMD64_SYSCFG 的第 23 位。

  • Active(活跃):支持、已启用,且 Linux 内核正在积极将加密位应用到页表项(内核中的 SME 掩码非零)。

SME 也可以在 BIOS 中启用和激活。如果在 BIOS 中启用并激活了 SME,则所有内存访问都将被加密,并且无需激活 Linux 内存加密支持。

如果 BIOS 仅仅启用了 SME(设置了 MSR_AMD64_SYSCFG 的第 23 位),那么可以通过在内核命令行中提供 mem_encrypt=on 来启用内存加密。但是,如果 BIOS 未启用 SME,则 Linux 将无法激活内存加密,即使默认配置为这样或指定了 mem_encrypt=on 命令行参数也是如此。

19.1. 安全嵌套页表 (SNP)

SEV-SNP 引入了新功能(SEV_FEATURES[1:63]),hypervisor 可以启用这些功能以增强安全性。其中某些功能需要来宾侧进行实现才能正常工作。下表列出了在来宾/hypervisor 对 SNP 功能支持的各种可能场景下,预期的来宾行为。

HV 启用的功能

来宾需要实现

来宾已实现

来宾启动行为

启动

启动

启动

启用功能后启动

优雅的启动失败

启用功能后启动

更多细节请参见 AMD64 APM[1] 第 2 卷:15.34.10 SEV_STATUS MSR

19.2. 反向映射表 (RMP)

RMP 是系统内存中的一种结构,用于确保系统物理地址和来宾物理地址之间的一对一映射。可能分配给来宾的每个内存页在 RMP 中都有一个条目。

RMP 表可以是在内存中连续的,也可以是内存中各个段的集合。

19.2.1. 连续 RMP

当支持 SEV-SNP 时,即支持这种形式的 RMP,这可以使用 CPUID 指令来确定

0x8000001f[eax]:
        Bit[4] indicates support for SEV-SNP

RMP 的位置通过两个 MSR 标识给硬件

0xc0010132 (RMP_BASE):
        System physical address of the first byte of the RMP

0xc0010133 (RMP_END):
        System physical address of the last byte of the RMP

硬件要求 RMP_BASE 和 (RPM_END + 1) 按 8KB 对齐,但 SEV 固件提高了对齐要求,需要 1MB 对齐。

RMP 由一个用于处理器记账的 16KB 区域以及大小为 16 字节的 RMP 条目组成。RMP 的大小决定了 hypervisor 可以分配给 SEV-SNP 来宾的物理内存范围。RMP 涵盖的系统物理地址从

0 to ((RMP_END + 1 - RMP_BASE - 16KB) / 16B) x 4KB.

当前的 Linux 支持依赖于 BIOS 来为 RMP 分配/保留内存,并适当地设置 RMP_BASE 和 RMP_END。Linux 使用 MSR 值来定位 RMP 并确定 RMP 的大小。为了让 Linux 启用 SEV-SNP,RMP 必须涵盖所有系统内存。

19.2.2. 分段 RMP

分段 RMP 支持是表示 RMP 布局的一种新方法。最初的 RMP 支持要求 RMP 表在内存中是连续的。从 RMP 不在其上的 NUMA 节点访问 RMP,可能比从 RMP 所在的 NUMA 节点访问花费更长的时间。分段 RMP 支持允许 RMP 条目位于与 RMP 所涵盖的内存相同的节点上,从而有可能减少与访问与该内存关联的 RMP 条目相关的延迟。每个 RMP 段涵盖特定的系统物理地址范围。

对这种形式的 RMP 的支持可以使用 CPUID 指令来确定

0x8000001f[eax]:
        Bit[23] indicates support for segmented RMP

如果支持,可以使用 CPUID 指令找到分段 RMP 属性

0x80000025[eax]:
        Bits[5:0]  minimum supported RMP segment size
        Bits[11:6] maximum supported RMP segment size

0x80000025[ebx]:
        Bits[9:0]  number of cacheable RMP segment definitions
        Bit[10]    indicates if the number of cacheable RMP segments
                   is a hard limit

为了启用分段 RMP,提供了一个新的 MSR

0xc0010136 (RMP_CFG):
        Bit[0]     indicates if segmented RMP is enabled
        Bits[13:8] contains the size of memory covered by an RMP
                   segment (expressed as a power of 2)

RMP_CFG MSR 中定义的 RMP 段大小适用于 RMP 的所有段。因此,每个 RMP 段涵盖特定的系统物理地址范围。例如,如果 RMP_CFG MSR 值为 0x2401,则 RMP 段覆盖值为 0x24 => 36,这意味着一个 RMP 段覆盖的内存大小为 64GB (1 << 36)。所以第一个 RMP 段涵盖从 0 到 0xF_FFFF_FFFF 的物理地址,第二个 RMP 段涵盖从 0x10_0000_0000 到 0x1F_FFFF_FFFF 的物理地址,依此类推。

启用分段 RMP 时,RMP_BASE 像现在一样指向 RMP 记账区域(大小为 16K)。但是,RMP 条目并不是紧接在记账区域之后开始的,而是有一个 4K 的 RMP 段表(RST)。RST 中的每个条目大小为 8 字节,并代表一个 RMP 段

Bits[19:0]  mapped size (in GB)
            The mapped size can be less than the defined segment size.
            A value of zero, indicates that no RMP exists for the range
            of system physical addresses associated with this segment.
Bits[51:20] segment physical address
            This address is left shift 20-bits (or just masked when
            read) to form the physical address of the segment (1MB
            alignment).

RST 可以容纳 512 个段条目,但如果可缓存 RMP 段的数量是一个硬性限制(CPUID 0x80000025_EBX[10]),则其大小可以限制为可缓存 RMP 段的数量(CPUID 0x80000025_EBX[9:0])。

当前的 Linux 支持依赖于 BIOS 来为分段 RMP 分配/保留内存(记账区域、RST 和所有段)、构建 RST,并适当地设置 RMP_BASE、RMP_END 和 RMP_CFG。Linux 使用 MSR 值来定位 RMP 并确定 RMP 段的大小和位置。为了让 Linux 启用 SEV-SNP,RMP 必须涵盖所有系统内存。

更多细节请参见 AMD64 APM 第 2 卷“15.36.3 Reverse Map Table”小节,docID: 24593。

19.3. 安全虚拟机服务模块 (SVSM)

SNP 提供了一项名为虚拟机特权级别(VMPL)的功能,该功能定义了四个特权级别,来宾软件可以在这些级别上运行。最高特权级别为 0,数字越大的特权越低。更多细节请参见 AMD64 APM 第 2 卷“15.35.7 Virtual Machine Privilege Levels”小节,docID: 24593。

使用该功能时,不同的服务可以在不同的保护级别上运行,它们独立于来宾操作系统,但仍处于安全的 SNP 环境中。它们可以向来宾提供服务,例如 vTPM。

当来宾未在 VMPL0 运行时代,它需要与在 VMPL0 运行的软件通信以执行特权操作或与安全服务交互。此类特权操作的一个例子是 PVALIDATE,它必须在 VMPL0 下执行。

在这种情况下,在 VMPL0 运行的软件通常称为安全虚拟机服务模块 (SVSM)。关于 SVSM 的发现以及用于与其通信的 API 记录在“Secure VM Service Module for SEV-SNP Guests”(docID: 58019)中。

(上述文档的最新版本可以通过使用像 duckduckgo.com 这样的搜索引擎并输入

site:amd.com “Secure VM Service Module for SEV-SNP Guests”, docID: 58019

例如来查找。)