安全加密虚拟化 (SEV)¶
概述¶
安全加密虚拟化 (SEV) 是 AMD 处理器上的一项功能。
SEV 是 AMD-V 架构的扩展,支持在虚拟机监控程序控制下运行虚拟机 (VM)。启用后,虚拟机的内存内容将使用该虚拟机独有的密钥透明地加密。
虚拟机监控程序可以通过 CPUID 指令确定 SEV 支持。CPUID 函数 0x8000001f 报告与 SEV 相关的信息
0x8000001f[eax]:
Bit[1] indicates support for SEV
...
[ecx]:
Bits[31:0] Number of encrypted guests supported simultaneously
如果存在对 SEV 的支持,则可以使用 MSR 0xc001_0010 (MSR_AMD64_SYSCFG) 和 MSR 0xc001_0015 (MSR_K7_HWCR) 来确定是否可以启用它
0xc001_0010:
Bit[23] 1 = memory encryption can be enabled
0 = memory encryption can not be enabled
0xc001_0015:
Bit[0] 1 = memory encryption can be enabled
0 = memory encryption can not be enabled
当 SEV 支持可用时,可以通过在执行 VMRUN 之前设置 SEV 位来在特定虚拟机中启用它。
VMCB[0x90]:
Bit[1] 1 = SEV is enabled
0 = SEV is disabled
SEV 硬件使用 ASID 将内存加密密钥与虚拟机关联。因此,启用 SEV 的访客的 ASID 必须介于 1 到 CPUID 0x8000001f[ecx] 字段中定义的最大值之间。
KVM_MEMORY_ENCRYPT_OP ioctl¶
访问 SEV 的主要 ioctl 是 KVM_MEMORY_ENCRYPT_OP,它在 VM 文件描述符上运行。如果 KVM_MEMORY_ENCRYPT_OP 的参数为 NULL,则如果启用了 SEV,ioctl 将返回 0,如果禁用了 SEV,则返回 ENOTTY
(在某些较旧版本的 Linux 上,即使使用 NULL 参数,ioctl 也会尝试正常运行,因此,如果启用了 SEV,则很可能会返回 EFAULT
而不是零)。如果非 NULL,则 KVM_MEMORY_ENCRYPT_OP 的参数必须是 struct kvm_sev_cmd
struct kvm_sev_cmd {
__u32 id;
__u64 data;
__u32 error;
__u32 sev_fd;
};
id
字段包含子命令,data
字段指向另一个包含特定于命令的参数的结构。sev_fd
应该指向在 /dev/sev
设备上打开的文件描述符(如果需要)(请参阅各个命令)。
在输出时,error
在成功时为零,或为一个错误代码。错误代码在 <linux/psp-dev.h>
中定义。
KVM 实现了以下命令来支持 SEV 访客的常见生命周期事件,例如启动、运行、快照、迁移和停用。
1. KVM_SEV_INIT2¶
虚拟机监控程序使用 KVM_SEV_INIT2 命令初始化 SEV 平台上下文。在典型的工作流程中,此命令应该是发出的第一个命令。
要接受此命令,必须将 KVM_X86_SEV_VM 或 KVM_X86_SEV_ES_VM 传递给 KVM_CREATE_VM ioctl。在调用 KVM_SEV_INIT2 之前,使用这些机器类型创建的虚拟机反过来不能运行。
参数:struct kvm_sev_init (in)
返回:成功时为 0,错误时为 -负数
struct kvm_sev_init {
__u64 vmsa_features; /* initial value of features field in VMSA */
__u32 flags; /* must be 0 */
__u16 ghcb_version; /* maximum guest GHCB version allowed */
__u16 pad1;
__u32 pad2[8];
};
如果虚拟机监控程序不支持在 flags
或 vmsa_features
中设置的任何位,则会发生错误。对于 SEV 虚拟机,vmsa_features
必须为 0,因为它们没有 VMSA。
对于 SEV 虚拟机,ghcb_version
必须为 0,因为它们不发出 GHCB 请求。如果对于任何其他访客类型,ghcb_version
为 0,则允许的最大访客 GHCB 协议将默认为版本 2。
此命令取代了已弃用的 KVM_SEV_INIT 和 KVM_SEV_ES_INIT 命令。这些命令没有任何参数(未使用 `data`
字段),并且仅适用于 KVM_X86_DEFAULT_VM 机器类型 (0)。
它们的行为就像
对于 KVM_SEV_INIT,虚拟机类型为 KVM_X86_SEV_VM,对于 KVM_SEV_ES_INIT,虚拟机类型为 KVM_X86_SEV_ES_VM
struct kvm_sev_init
的flags
和vmsa_features
字段设置为零,对于 KVM_SEV_INIT,ghcb_version
设置为 0,对于 KVM_SEV_ES_INIT,设置为 1。
如果 KVM_X86_SEV_VMSA_FEATURES
属性不存在,则虚拟机监控程序仅支持 KVM_SEV_INIT 和 KVM_SEV_ES_INIT。在这种情况下,请注意,KVM_SEV_ES_INIT 可能会根据 kvm-amd.ko
的 debug_swap
参数的值来设置调试交换 VMSA 功能(位 5)。
2. KVM_SEV_LAUNCH_START¶
KVM_SEV_LAUNCH_START 命令用于创建内存加密上下文。要创建加密上下文,用户必须提供访客策略、所有者的公钥 Diffie-Hellman (PDH) 密钥和会话信息。
参数:struct kvm_sev_launch_start (in/out)
返回:成功时为 0,错误时为 -负数
struct kvm_sev_launch_start {
__u32 handle; /* if zero then firmware creates a new handle */
__u32 policy; /* guest's policy */
__u64 dh_uaddr; /* userspace address pointing to the guest owner's PDH key */
__u32 dh_len;
__u64 session_addr; /* userspace address which points to the guest session information */
__u32 session_len;
};
成功时,“handle”字段包含新的句柄,错误时包含负值。
KVM_SEV_LAUNCH_START 要求 sev_fd
字段有效。
有关更多详细信息,请参阅 SEV 规范第 6.2 节。
3. KVM_SEV_LAUNCH_UPDATE_DATA¶
KVM_SEV_LAUNCH_UPDATE_DATA 用于加密内存区域。它还会计算内存内容的度量值。度量值是内存内容的签名,可以将其发送给访客所有者,作为固件正确加密内存的证明。
参数 (in):struct kvm_sev_launch_update_data
返回:成功时为 0,错误时为 -负数
struct kvm_sev_launch_update {
__u64 uaddr; /* userspace address to be encrypted (must be 16-byte aligned) */
__u32 len; /* length of the data to be encrypted (must be 16-byte aligned) */
};
有关更多详细信息,请参阅 SEV 规范第 6.3 节。
4. KVM_SEV_LAUNCH_MEASURE¶
KVM_SEV_LAUNCH_MEASURE 命令用于检索由 KVM_SEV_LAUNCH_UPDATE_DATA 命令加密的数据的度量值。访客所有者可以等到它验证度量值后,再向访客提供机密信息。由于访客所有者知道访客在启动时的初始内容,因此可以通过将其与访客所有者的预期内容进行比较来验证度量值。
如果条目上的 len 为零,则度量值 blob 长度将写入 len,并且 uaddr 未使用。
参数 (in):struct kvm_sev_launch_measure
返回:成功时为 0,错误时为 -负数
struct kvm_sev_launch_measure {
__u64 uaddr; /* where to copy the measurement */
__u32 len; /* length of measurement blob */
};
有关度量值验证流程的更多详细信息,请参阅 SEV 规范第 6.4 节。
5. KVM_SEV_LAUNCH_FINISH¶
在完成启动流程后,可以发出 KVM_SEV_LAUNCH_FINISH 命令,使访客准备好执行。
返回:成功时为 0,错误时为 -负数
6. KVM_SEV_GUEST_STATUS¶
KVM_SEV_GUEST_STATUS 命令用于检索有关启用 SEV 的访客的状态信息。
参数 (out):struct kvm_sev_guest_status
返回:成功时为 0,错误时为 -负数
struct kvm_sev_guest_status {
__u32 handle; /* guest handle */
__u32 policy; /* guest policy */
__u8 state; /* guest state (see enum below) */
};
SEV 访客状态
enum {
SEV_STATE_INVALID = 0;
SEV_STATE_LAUNCHING, /* guest is currently being launched */
SEV_STATE_SECRET, /* guest is being launched and ready to accept the ciphertext data */
SEV_STATE_RUNNING, /* guest is fully launched and running */
SEV_STATE_RECEIVING, /* guest is being migrated in from another SEV machine */
SEV_STATE_SENDING /* guest is getting migrated out to another SEV machine */
};
7. KVM_SEV_DBG_DECRYPT¶
虚拟机监控程序可以使用 KVM_SEV_DEBUG_DECRYPT 命令请求固件解密给定内存区域的数据。
参数 (in):struct kvm_sev_dbg
返回:成功时为 0,错误时为 -负数
struct kvm_sev_dbg {
__u64 src_uaddr; /* userspace address of data to decrypt */
__u64 dst_uaddr; /* userspace address of destination */
__u32 len; /* length of memory region to decrypt */
};
如果访客策略不允许调试,该命令将返回错误。
8. KVM_SEV_DBG_ENCRYPT¶
虚拟机监控程序可以使用 KVM_SEV_DEBUG_ENCRYPT 命令请求固件加密给定内存区域的数据。
参数 (in):struct kvm_sev_dbg
返回:成功时为 0,错误时为 -负数
struct kvm_sev_dbg {
__u64 src_uaddr; /* userspace address of data to encrypt */
__u64 dst_uaddr; /* userspace address of destination */
__u32 len; /* length of memory region to encrypt */
};
如果访客策略不允许调试,该命令将返回错误。
9. KVM_SEV_LAUNCH_SECRET¶
在访客所有者验证度量值后,虚拟机监控程序可以使用 KVM_SEV_LAUNCH_SECRET 命令注入秘密数据。
参数 (in):struct kvm_sev_launch_secret
返回:成功时为 0,错误时为 -负数
struct kvm_sev_launch_secret {
__u64 hdr_uaddr; /* userspace address containing the packet header */
__u32 hdr_len;
__u64 guest_uaddr; /* the guest memory region where the secret should be injected */
__u32 guest_len;
__u64 trans_uaddr; /* the hypervisor memory region which contains the secret */
__u32 trans_len;
};
10. KVM_SEV_GET_ATTESTATION_REPORT¶
虚拟机监控程序可以使用 KVM_SEV_GET_ATTESTATION_REPORT 命令来查询证明报告,其中包含通过 KVM_SEV_LAUNCH 命令传递的访客内存和 VMSA 的 SHA-256 摘要,并使用 PEK 进行签名。该命令返回的摘要应与访客所有者使用 KVM_SEV_LAUNCH_MEASURE 的摘要匹配。
如果条目上的 len 为零,则度量值 blob 长度将写入 len,并且 uaddr 未使用。
参数 (in):struct kvm_sev_attestation
返回:成功时为 0,错误时为 -负数
struct kvm_sev_attestation_report {
__u8 mnonce[16]; /* A random mnonce that will be placed in the report */
__u64 uaddr; /* userspace address where the report should be copied */
__u32 len;
};
11. KVM_SEV_SEND_START¶
虚拟机监控程序可以使用 KVM_SEV_SEND_START 命令创建传出的访客加密上下文。
如果条目上的 session_len 为零,则访客会话信息的长度将写入 session_len,并且不会使用所有其他字段。
参数 (in):struct kvm_sev_send_start
返回:成功时为 0,错误时为 -负数
struct kvm_sev_send_start {
__u32 policy; /* guest policy */
__u64 pdh_cert_uaddr; /* platform Diffie-Hellman certificate */
__u32 pdh_cert_len;
__u64 plat_certs_uaddr; /* platform certificate chain */
__u32 plat_certs_len;
__u64 amd_certs_uaddr; /* AMD certificate */
__u32 amd_certs_len;
__u64 session_uaddr; /* Guest session information */
__u32 session_len;
};
12. KVM_SEV_SEND_UPDATE_DATA¶
虚拟机管理程序可以使用 KVM_SEV_SEND_UPDATE_DATA 命令,利用使用 KVM_SEV_SEND_START 创建的加密上下文来加密传出的客户机内存区域。
如果进入时 hdr_len 或 trans_len 为零,则数据包头和传输区域的长度将分别写入 hdr_len 和 trans_len,所有其他字段不使用。
参数(输入):struct kvm_sev_send_update_data
返回:成功时为 0,错误时为 -负数
struct kvm_sev_launch_send_update_data {
__u64 hdr_uaddr; /* userspace address containing the packet header */
__u32 hdr_len;
__u64 guest_uaddr; /* the source memory region to be encrypted */
__u32 guest_len;
__u64 trans_uaddr; /* the destination memory region */
__u32 trans_len;
};
13. KVM_SEV_SEND_FINISH¶
在迁移流程完成后,虚拟机管理程序可以发出 KVM_SEV_SEND_FINISH 命令来删除加密上下文。
返回:成功时为 0,错误时为 -负数
14. KVM_SEV_SEND_CANCEL¶
在完成 SEND_START 之后,但在 SEND_FINISH 之前,源 VMM 可以发出 SEND_CANCEL 命令来停止迁移。 这是必要的,以便取消的迁移可以在稍后使用新的目标重新启动。
返回:成功时为 0,错误时为 -负数
15. KVM_SEV_RECEIVE_START¶
KVM_SEV_RECEIVE_START 命令用于为传入的 SEV 客户机创建内存加密上下文。 要创建加密上下文,用户必须提供客户机策略、平台公共 Diffie-Hellman (PDH) 密钥和会话信息。
参数:struct kvm_sev_receive_start (输入/输出)
返回:成功时为 0,错误时为 -负数
struct kvm_sev_receive_start {
__u32 handle; /* if zero then firmware creates a new handle */
__u32 policy; /* guest's policy */
__u64 pdh_uaddr; /* userspace address pointing to the PDH key */
__u32 pdh_len;
__u64 session_uaddr; /* userspace address which points to the guest session information */
__u32 session_len;
};
成功时,“handle”字段包含新的句柄,错误时包含负值。
有关更多详细信息,请参阅 SEV 规范第 6.12 节。
16. KVM_SEV_RECEIVE_UPDATE_DATA¶
虚拟机管理程序可以使用 KVM_SEV_RECEIVE_UPDATE_DATA 命令,将传入的缓冲区复制到在 KVM_SEV_RECEIVE_START 期间创建的具有加密上下文的客户机内存区域中。
参数(输入):struct kvm_sev_receive_update_data
返回:成功时为 0,错误时为 -负数
struct kvm_sev_launch_receive_update_data {
__u64 hdr_uaddr; /* userspace address containing the packet header */
__u32 hdr_len;
__u64 guest_uaddr; /* the destination guest memory region */
__u32 guest_len;
__u64 trans_uaddr; /* the incoming buffer memory region */
__u32 trans_len;
};
17. KVM_SEV_RECEIVE_FINISH¶
在迁移流程完成后,虚拟机管理程序可以发出 KVM_SEV_RECEIVE_FINISH 命令,使客户机准备好执行。
返回:成功时为 0,错误时为 -负数
18. KVM_SEV_SNP_LAUNCH_START¶
KVM_SNP_LAUNCH_START 命令用于为 SEV-SNP 客户机创建内存加密上下文。它必须在发出 KVM_SEV_SNP_LAUNCH_UPDATE 或 KVM_SEV_SNP_LAUNCH_FINISH 之前调用;
参数(输入):struct kvm_sev_snp_launch_start
返回:成功时为 0,错误时为 -负数
struct kvm_sev_snp_launch_start {
__u64 policy; /* Guest policy to use. */
__u8 gosvw[16]; /* Guest OS visible workarounds. */
__u16 flags; /* Must be zero. */
__u8 pad0[6];
__u64 pad1[4];
};
有关 struct kvm_sev_snp_launch_start
中输入参数的更多详细信息,请参阅 SEV-SNP 规范中的 SNP_LAUNCH_START [snp-fw-abi]。
19. KVM_SEV_SNP_LAUNCH_UPDATE¶
KVM_SEV_SNP_LAUNCH_UPDATE 命令用于将用户空间提供的数据加载到客户机 GPA 范围中,将内容度量到由 KVM_SEV_SNP_LAUNCH_START 创建的 SNP 客户机上下文中,然后加密/验证该 GPA 范围,以便在使用与客户机上下文关联的加密密钥启动后立即读取,此后它可以证明与其上下文关联的度量,然后解锁任何密钥。
此命令初始化的 GPA 范围必须预先设置 KVM_MEMORY_ATTRIBUTE_PRIVATE 属性。 有关这方面的更多详细信息,请参阅 KVM_SET_MEMORY_ATTRIBUTES 的文档。
成功后,不能保证此命令已处理请求的整个范围。相反,struct kvm_sev_snp_launch_update
的 gfn_start
、uaddr
和 len
字段将更新以对应于尚未处理的剩余范围。调用者应继续调用此命令,直到这些字段指示已处理整个范围,例如 len
为 0,gfn_start
等于范围中最后一个 GFN 加 1,uaddr
是用户空间提供的源缓冲区地址的最后一个字节加 1。如果 type
为 KVM_SEV_SNP_PAGE_TYPE_ZERO,则将完全忽略 uaddr
。
参数(输入):struct kvm_sev_snp_launch_update
返回值:成功返回 0,错误返回 < 0,如果调用者应重试,则返回 -EAGAIN
struct kvm_sev_snp_launch_update {
__u64 gfn_start; /* Guest page number to load/encrypt data into. */
__u64 uaddr; /* Userspace address of data to be loaded/encrypted. */
__u64 len; /* 4k-aligned length in bytes to copy into guest memory.*/
__u8 type; /* The type of the guest pages being initialized. */
__u8 pad0;
__u16 flags; /* Must be zero. */
__u32 pad1;
__u64 pad2[4];
};
其中,page_type 的允许值定义为 #define
KVM_SEV_SNP_PAGE_TYPE_NORMAL
KVM_SEV_SNP_PAGE_TYPE_ZERO
KVM_SEV_SNP_PAGE_TYPE_UNMEASURED
KVM_SEV_SNP_PAGE_TYPE_SECRETS
KVM_SEV_SNP_PAGE_TYPE_CPUID
有关如何使用/度量每个页面类型的更多详细信息,请参阅 SEV-SNP 规范 [snp-fw-abi]。
20. KVM_SEV_SNP_LAUNCH_FINISH¶
在 SNP 客户机启动流程完成后,可以发出 KVM_SEV_SNP_LAUNCH_FINISH 命令,使客户机准备好执行。
参数(输入):struct kvm_sev_snp_launch_finish
返回:成功时为 0,错误时为 -负数
struct kvm_sev_snp_launch_finish {
__u64 id_block_uaddr;
__u64 id_auth_uaddr;
__u8 id_block_en;
__u8 auth_key_en;
__u8 vcek_disabled;
__u8 host_data[32];
__u8 pad0[3];
__u16 flags; /* Must be zero */
__u64 pad1[4];
};
有关 struct kvm_sev_snp_launch_finish
中输入参数的更多详细信息,请参阅 SEV-SNP 规范中的 SNP_LAUNCH_FINISH [snp-fw-abi]。
设备属性 API¶
SEV 实现的属性可以通过 /dev/kvm
设备节点上的 KVM_HAS_DEVICE_ATTR
和 KVM_GET_DEVICE_ATTR
ioctl 来检索,使用组 KVM_X86_GRP_SEV
。
目前只实现了一个属性
KVM_X86_SEV_VMSA_FEATURES
:返回KVM_SEV_INIT2
的vmsa_features
中接受的所有位集合。
固件管理¶
SEV 客户机密钥管理由一个单独的处理器处理,称为 AMD 安全处理器 (AMD-SP)。 在 AMD-SP 内运行的固件提供了一个安全的密钥管理接口,用于执行常见的虚拟机管理程序活动,例如加密引导代码、快照、迁移和调试客户机。 有关更多信息,请参阅 SEV 密钥管理规范 [api-spec]
AMD-SP 固件可以使用其自身的非易失性存储器进行初始化,或者操作系统可以使用 ccp
模块的参数 init_ex_path
来管理固件的 NV 存储。 如果 init_ex_path
指定的文件不存在或无效,则操作系统将使用 PSP 非易失性存储器创建或覆盖该文件。
参考文献¶
有关更多信息,请参阅 [white-paper]、[api-spec]、[amd-apm]、[kvm-forum] 和 [snp-fw-abi]。