19. AMD 内存加密

安全内存加密 (SME) 和安全加密虚拟化 (SEV) 是 AMD 处理器上的特性。

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

SEV 允许运行加密虚拟机 (VM),其中来宾 VM 的代码和数据受到保护,因此只有在 VM 本身中才能获得解密版本。 SEV 来宾 VM 具有私有内存和共享内存的概念。 私有内存使用来宾特定密钥加密,而共享内存可以使用虚拟机监控程序密钥加密。 启用 SME 后,虚拟机监控程序密钥与 SME 中使用的密钥相同。

当页表条目设置了加密位时,该页会被加密(请参阅下文,了解如何确定其位置)。 加密位也可以在 cr3 寄存器中指定,从而允许加密 PGD 表。 通过在指向下一个页表的页表条目中设置加密位,也可以加密每个连续级别的页表。 这允许加密整个页表层次结构。 请注意,这表示仅仅因为 cr3 中设置了加密位,并不意味着整个层次结构都被加密。 层次结构中的每个页表条目都需要设置加密位才能实现这一点。 因此,从理论上讲,您可以在 cr3 中设置加密位以便加密 PGD,但不在 PGD 条目中设置 PUD 的加密位,这会导致该条目指向的 PUD 不被加密。

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

可以通过 CPUID 指令确定对 SME 和 SEV 的支持。 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

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

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

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

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

  • 活动:支持、已启用且 Linux 内核正在主动将加密位应用于页表条目(内核中的 SME 掩码非零)。

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

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

19.1. 安全嵌套分页 (SNP)

SEV-SNP 引入了新特性 (SEV_FEATURES[1:63]),虚拟机监控程序可以启用这些特性以增强安全性。 其中一些特性需要来宾端实现才能正常运行。 下表列出了来宾/虚拟机监控程序 SNP 特性支持的各种可能场景下,预期的来宾行为。

由 HV 启用的特性

来宾需要实现

来宾具有实现

来宾启动行为

启动

启动

启动

启动时启用特性

优雅的启动失败

启动时启用特性

更多详细信息请参见 AMD64 APM[1] Vol 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

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

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 区域和随后的 RMP 条目组成,RMP 条目的大小为 16 字节。 RMP 的大小决定了虚拟机监控程序可以分配给 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 指令确定对此形式的 RMP 的支持

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 Vol 2,section “15.36.3 Reverse Map Table”,docID: 24593。

19.3. 安全 VM 服务模块 (SVSM)

SNP 提供了一个称为虚拟机特权级别 (VMPL) 的特性,该特性定义了来宾软件可以运行的四个特权级别。 特权最高的级别为 0,数值较高的数字具有较低的特权。 更多详细信息请参见 AMD64 APM Vol 2,section “15.35.7 Virtual Machine Privilege Levels”,docID: 24593。

使用该特性时,不同的服务可以在不同的保护级别运行,与来宾操作系统分离,但仍然在安全的 SNP 环境中。 它们可以为来宾提供服务,例如 vTPM。

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

在这种情况下,在 VMPL0 运行的软件通常称为安全 VM 服务模块 (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

例如。)