Linux 内核 x86 架构:AMD 内存加密(SME/SEV/SNP/RMP/SVSM)深度解析
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
本文围绕 Linux 内核仓库中的 AMD Memory Encryption 文档 展开,系统讲解 AMD 处理器 Secure Memory Encryption(SME)、Secure Encrypted Virtualization(SEV)、Secure Nested Paging(SNP)、Reverse Map Table(RMP)与 Secure VM Service Module(SVSM)的硬件机制、CPUID/MSR 接口以及内核中的落地方式。读完本篇,你将能够理解内核如何通过页表加密位保护 DRAM、如何在启动参数上启用mem_encrypt=on,并在源码层面定位到 AMD 内存加密实现 与 SEV 来宾机密计算支持 的具体代码位置。
一、SME 与 SEV 是什么
SME 和 SEV 是 AMD 处理器提供的两项内存保护特性:
- SME(Secure Memory Encryption):通过标准 x86 页表将单个物理内存页标记为"加密"。被标记的页在读出 DRAM 时由硬件自动解密、写回 DRAM 时自动加密,因此可以防御针对系统 DRAM 的物理攻击。
- SEV(Secure Encrypted Virtualization):允许运行"加密虚拟机"。客户机(guest)的代码与数据被保护,解密后的版本只在 VM 内部可见。SEV guest 区分私有内存(private,用 guest 专属密钥加密)与共享内存(shared,可用 hypervisor 密钥加密);当 SME 开启时,hypervisor 密钥就是 SME 使用的密钥。
内核中对应的 Kconfig 依赖关系可以印证这两项特性是一体的:
config X86_MEM_ENCRYPT select ARCH_HAS_FORCE_DMA_UNENCRYPTED select DYNAMIC_PHYSICAL_MASK def_bool n config AMD_MEM_ENCRYPT bool "AMD Secure Memory Encryption (SME) support" depends on X86_64 && CPU_SUP_AMD depends on EFI_STUB select DMA_COHERENT_POOL select ARCH_USE_MEMREMAP_PROT select INSTRUCTION_DECODER select ARCH_HAS_CC_PLATFORM select X86_MEM_ENCRYPT select UNACCEPTED_MEMORY select CRYPTO_LIB_AES_GCM(见 arch/x86/Kconfig)从源码结构看,SME 支持仅在 64 位、AMD CPU 且带 EFI_STUB 时可选,并顺带选择了ARCH_HAS_CC_PLATFORM(机密计算平台抽象),说明内核把 SME/SEV 统一纳入了confidential computing(CC)框架。
二、加密位(CBit)的工作机制
页的加密由页表项中的"加密位"(encryption bit)控制,其位置不是固定的,需要通过 CPUID 查询(见下文0x8000001f[ebx])。加密位的使用有两条路径:
- 加密位可以直接写进页表项(PTE/PDE 等),加密对应的内存页;
- 加密位也可以设置在
CR3寄存器中,从而使 PGD(页全局目录)本身被加密。
进一步地,页表的每一级都可以被加密:只要指向下一级表的页表项中设置了加密位,那一级页表就会被加密。这样整个页表层级(full page table hierarchy)都可以被加密。文档特别强调了一个易错点:CR3 里设置了加密位,并不意味着整个层级都被加密。例如 CR3 设了加密位、PGD 被加密,但 PGD 中指向某个 PUD 的表项若未设加密位,则该 PUD 表不会被加密。每一级页表项都要独立设置加密位才能达到"全层级加密"。
SEV 场景下的硬件行为有三条规则:
- SEV 开启时,指令页(instruction pages)和 guest 页表始终被视为私有;
- guest 内所有 DMA 操作必须落在共享内存上(内核 Kconfig 中
ARCH_HAS_FORCE_DMA_UNENCRYPTED正是为此服务,强制 DMA 缓冲走未加密的共享区域); - 加密位本身在 64 位或 32 位 PAE 模式下由 guest OS 控制;在其它寻址模式下,SEV 硬件会强制把内存加密位置 1。
内核中加密位的位掩码(mask)由 arch/x86/mm/mem_encrypt_amd.c 在启动时初始化,页表操作通过arch/x86/include/asm/mem_encrypt.h提供的宏(如set_memory_encrypted等)应用该掩码;mem_encrypt=on这一启动参数则早在压缩启动阶段就被解析,见 arch/x86/boot/compressed/misc.c 中的cmdline_find_option_bool("mem_encrypt=on")。此外,arch/x86/kernel/sev_verify_cbit.S 提供内联汇编原语,用于在 SEV 来宾中校验加密位是否按预期生效。
三、CPUID 与 MSR 接口
3.1 CPUID 0x8000001f:特性探测
通过 CPUID 指令判断 CPU 对 SME/SEV 的支持:
0x8000001f[eax]: Bit[0] 支持 SME Bit[1] 支持 SEV 0x8000001f[ebx]: Bits[5:0] 页表中用于激活内存加密的位编号(即 CBit 位置) Bits[11:6] 启用内存加密后物理地址空间的缩减位数 (只影响系统物理地址,不影响 guest 物理地址)同一 eax 中后续还有:
Bit[4]:支持 SEV-SNP(连续 RMP 的前提);Bit[23]:支持分段 RMP(segmented RMP)。
3.2 MSR 0xc0010010(MSR_AMD64_SYSCFG):SME 使能位
若 CPU 支持 SME,MSR0xc0010010(MSR_AMD64_SYSCFG)用于查询/使能内存加密:
0xc0010010: Bit[23] 0 = 内存加密特性禁用 1 = 内存加密特性启用Linux 依赖 BIOS 来设置这一位:BIOS 需要判断"启用加密导致的物理地址空间缩减"(由 CPUID 报告的缩减位数决定)与系统地址空间资源需求不冲突后才会置位。如果 Linux 启动时该位未设置,Linux 自身不会去置位,内存加密也就无法启用。这是部署 SME 时最常见的前提限制。
3.3 MSR 0xc0010131(MSR_AMD64_SEV):SEV 激活状态
若 CPU 支持 SEV,MSR0xc0010131(MSR_AMD64_SEV)指示 SEV 是否激活:
0xc0010131: Bit[0] 0 = 内存加密未激活 1 = 内存加密已激活3.4 SME 在 Linux 中的三态模型
文档把 Linux 内核中 SME 的状态归纳为三级,排查问题时值得按此顺序核对:
| 状态 | 定义 |
|---|---|
| Supported | CPU 支持 SME(由 CPUID 指令确认) |
| Enabled | Supported 且 MSR_AMD64_SYSCFG 的 bit 23 已置位 |
| Active | Supported + Enabled,且 Linux 内核正在把加密位应用到页表项上(内核中的 SME mask 非零) |
四、启用 SME:mem_encrypt=on与 BIOS 的配合
SME 可以在 BIOS 中启用(enable)并激活(activate)。两种情形效果不同:
- BIOS 中同时启用并激活:所有内存访问都会被加密,无需再激活 Linux 的内存加密支持;
- BIOS 仅启用(只置 SYSCFG 的 bit 23):此时可通过内核命令行参数
mem_encrypt=on启用内存加密。
关键限制再次强调:如果 BIOS 根本没有启用 SME,Linux 无法激活内存加密——即使内核默认配置为启用、或显式传了mem_encrypt=on。因此mem_encrypt=on只在 "Supported && Enabled" 的前提下才有意义,它只是把 SME 推进到 "Active" 状态的手段。
启动流程上的源码落点:mem_encrypt=on先被压缩内核解析(arch/x86/boot/compressed/misc.c),随后arch/x86/mm/Makefile在CONFIG_AMD_MEM_ENCRYPT下编译mem_encrypt_amd.o与mem_encrypt_boot.o(见 arch/x86/mm/Makefile),arch/x86/mm/mem_encrypt.c 中的mem_encrypt_init()与mem_encrypt_setup_arch()完成特性信息打印与启动期设置。
五、SEV-SNP 与 guest 侧特性协商
SEV-SNP 引入了一组新特性(SEV_FEATURES[1:63]),可由 hypervisor 为增强安全性而启用,其中一些特性需要 guest 侧有对应实现才能正确工作。文档给出了 guest 启动行为随两侧支持情况变化的完整矩阵:
| Hypervisor 启用特性 | Guest 需要实现 | Guest 有实现 | Guest 启动行为 |
|---|---|---|---|
| No | No | No | 正常启动(Boot) |
| No | Yes | No | 正常启动(Boot) |
| No | Yes | Yes | 正常启动(Boot) |
| Yes | No | No | 带特性启用启动(Boot with feature enabled) |
| Yes | Yes | No | 优雅启动失败(Graceful boot failure) |
| Yes | Yes | Yes | 带特性启用启动(Boot with feature enabled) |
更多细节见 AMD64 APM Vol 2 第 15.34.10 节(SEV_STATUS MSR)。可以推断:只有当"hypervisor 启用了特性、guest 声明需要实现、但 guest 实际没有实现"时才会出现优雅失败,这正是防止特性被静默降级的安全设计。
六、Reverse Map Table(RMP)
RMP 是位于系统内存中的结构,用于保证系统物理地址(SPA)与 guest 物理地址(GPA)之间的一对一映射:每个可能分配给 guest 的内存页在 RMP 中都有一个表项。RMP 表在内存中可以是连续的,也可以是分段(segmented)的。
6.1 连续 RMP(Contiguous RMP)
连续 RMP 的支持以 SEV-SNP 支持为前提(CPUID0x8000001f[eax] Bit[4])。RMP 的位置通过两个 MSR 告知硬件:
0xc0010132 (RMP_BASE): RMP 首字节的系统物理地址 0xc0010133 (RMP_END): RMP 末字节的系统物理地址对齐要求:硬件要求RMP_BASE与(RMP_END + 1)8KB 对齐,而 SEV 固件把要求提高到1MB 对齐。
结构布局与容量计算:RMP 由 16KB 的处理器记账区(bookkeeping)加上 RMP 表项组成,每个表项 16 字节。RMP 的大小决定了 hypervisor 可分配给 SEV-SNP guest 的物理内存范围,其覆盖的系统物理地址区间为:
0 到 ((RMP_END + 1 - RMP_BASE - 16KB) / 16B) x 4KBLinux 侧的现状:依赖 BIOS 为 RMP 分配/预留内存并正确设置RMP_BASE、RMP_END;Linux 依据 MSR 值定位 RMP 并计算其大小。只有当 RMP 覆盖全部系统内存时,Linux 才会启用 SEV-SNP。
6.2 分段 RMP(Segmented RMP)
分段 RMP 是 RMP 布局的新表示方式。早期实现要求 RMP 表在内存中连续;而从远端 NUMA 节点访问 RMP 比从 RMP 所在节点访问更慢。分段 RMP 允许把 RMP 表项放在其覆盖内存所在的同一节点上,从而降低访问 RMP 表项的延迟。每个 RMP 段覆盖一段特定的系统物理地址范围。
能力探测(CPUID):
0x8000001f[eax]: Bit[23] 支持分段 RMP 若支持,段属性见: 0x80000025[eax]: Bits[5:0] 支持的最小 RMP 段大小 Bits[11:6] 支持的最大 RMP 段大小 0x80000025[ebx]: Bits[9:0] 可缓存(cacheable)RMP 段定义的数量 Bit[10] 该可缓存段数量是否为硬性上限启用分段 RMP 使用新 MSR:
0xc0010136 (RMP_CFG): Bit[0] 分段 RMP 是否启用 Bits[13:8] 每个 RMP 段覆盖的内存大小(2 的幂指数)RMP_CFG中的段大小适用于 RMP 的所有段。文档给出了一个具体例子:若RMP_CFG = 0x2401,则段覆盖值为0x24(即 36),每个段覆盖 64GB(1 << 36)内存。于是第一个段覆盖物理地址0到0xF_FFFF_FFFF,第二个段覆盖0x10_0000_0000到0x1F_FFFF_FFFF,依此类推。
布局上:启用分段 RMP 后,RMP_BASE仍指向 16K 的记账区,但记账区之后不再是紧跟的 RMP 表项,而是一个 4K 的 RMP 段表(RST,RMP Segment Table)。RST 每个表项 8 字节,表示一个 RMP 段:
Bits[19:0] 映射大小(单位 GB);可以小于定义的段大小。 为 0 表示该段对应的系统物理地址范围不存在 RMP。 Bits[51:20] 段的物理地址;左移 20 位(或读取时掩码) 即得段的物理地址(1MB 对齐)。RST 最多容纳 512 个段表项;但若 CPUID0x80000025_EBX[10]指示可缓存段数量是硬上限,则 RST 可被限制为0x80000025_EBX[9:0]所给出的数量。
与连续 RMP 相同,Linux 的分段 RMP 支持也依赖 BIOS 完成内存分配/预留(记账区、RST 与所有段)、构建 RST,并正确设置RMP_BASE、RMP_END、RMP_CFG;Linux 依据 MSR 值定位 RMP 段并计算大小与位置,且 RMP 必须覆盖全部系统内存,Linux 才会启用 SEV-SNP。更多细节见 AMD64 APM Vol 2 "15.36.3 Reverse Map Table" 一节(docID: 24593)。
七、SVSM:Secure VM Service Module
SNP 提供了虚拟机特权级(VMPL,Virtual Machine Privilege Levels)特性,定义 guest 软件可运行的四个特权级:数字越小特权越高,最高特权级为 0。不同服务可以运行在不同的保护级别上——位于 guest OS 之外、但仍在安全的 SNP 环境内——为 guest 提供例如 vTPM 之类的服务。
当 guest 不运行在 VMPL0 时,它需要与 VMPL0 上运行的软件通信,才能执行特权操作或访问安全服务。典型例子是PVALIDATE指令——它必须在 VMPL0 执行。
在这种场景下,运行在 VMPL0 的软件通常被称为SVSM(Secure VM Service Module)。SVSM 的发现(discovery)机制及其通信 API 在 AMD 文档 "Secure VM Service Module for SEV-SNP Guests"(docID: 58019)中有定义;VMPL 的细节见 AMD64 APM Vol 2 "15.35.7 Virtual Machine Privilege Levels"(docID: 24593)。
Linux 内核中已有对应的实现落点:arch/x86/coco/sev/svsm.c 实现了 guest 与 SVSM 的通信协议。从源码结构看,guest 通过 GHCB(Guest-Hypervisor Communication Block)发起SVM_VMGEXIT_SNP_RUN_VMPL类型的运行请求(svsm_perform_ghcb_protocol()中可见ghcb_set_sw_exit_code(ghcb, SVM_VMGEXIT_SNP_RUN_VMPL)),并为早期启动准备了身份映射的 CAA(Communication Access Area)页面boot_svsm_ca_page,以同时支持引导早期和常规内核虚拟地址两种场景。SEV 来宾的其余机密计算组件(VC 共享内存处理、core 初始化等)集中在 arch/x86/coco/sev/ 目录下(core.c、vc-shared.c、vc-handle.c等),通用 CC 平台抽象入口在 arch/x86/coco/core.c。
八、部署核对清单
综合文档与源码,在 AMD 平台上核对内存加密状态时可按以下顺序操作(均基于当前仓库文档与实现):
- 确认 BIOS 状态:检查 SYSCFG MSR(
0xc0010010)bit 23 是否已置位;若 BIOS 已"启用+激活",内核无需额外操作。 - 配置内核:确保
CONFIG_AMD_MEM_ENCRYPT=y(依赖X86_64、CPU_SUP_AMD、EFI_STUB,见 arch/x86/Kconfig)。 - (如需内核激活)传递启动参数:
mem_encrypt=on,仅在 BIOS 已启用但未激活 SME 时生效。 - 按三态模型核对:Supported(CPUID
0x8000001f[eax] Bit[0])→ Enabled(SYSCFG bit 23)→ Active(内核 SME mask 非零,页表项实际应用加密位)。 - SEV 来宾场景:确认 MSR
0xc0010131bit 0 表示加密激活;SNP 场景再确认 RMP 是否覆盖全部系统内存(连续 RMP 查RMP_BASE/RMP_END,分段 RMP 另查RMP_CFG),以及 guest 侧特性矩阵是否落入"正常启动"或"带特性启动"行。
需要牢记的前提与限制:Linux 从不自行设置 SYSCFG 的 SME 位;RMP(无论连续还是分段)的内存预留与 MSR 设置均由 BIOS 负责;guest 内 DMA 必须使用共享内存。这些约束决定了整个方案中 BIOS 固件与固件-内核分工是能否跑通的关键。
【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考