安全加密虚拟化 (SEV)¶
概述¶
安全加密虚拟化 (SEV) 是 AMD 处理器上的一项功能。
SEV 是 AMD-V 架构的扩展,支持在虚拟机监控程序的控制下运行虚拟机 (VM)。启用后,VM 的内存内容将被透明地加密,加密密钥对该 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 位在特定 VM 中启用它。
VMCB[0x90]:
Bit[1] 1 = SEV is enabled
0 = SEV is disabled
SEV 硬件使用 ASID 将内存加密密钥与 VM 相关联。因此,启用 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,如果禁用则返回 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,错误时为 -negative
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,VM 类型为 KVM_X86_SEV_VM,对于 KVM_SEV_ES_INIT,VM 类型为 KVM_X86_SEV_ES_VM
struct kvm_sev_init
的flags
和vmsa_features
字段设置为零,对于 KVM_SEV_INIT,ghcb_version
设置为 0,对于 KVM_SEV_ES_INIT,ghcb_version
设置为 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,错误时为 -negative
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 用于加密内存区域。它还会计算内存内容的度量值。该度量值是内存内容的签名,可以作为证明固件已正确加密内存发送给访客所有者。
参数(输入):struct kvm_sev_launch_update_data
返回值:成功时为 0,错误时为 -negative
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 未使用。
参数(输入):struct kvm_sev_launch_measure
返回值:成功时为 0,错误时为 -negative
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,错误时为 -negative
6. KVM_SEV_GUEST_STATUS¶
KVM_SEV_GUEST_STATUS 命令用于检索有关启用 SEV 的访客的状态信息。
参数(输出):struct kvm_sev_guest_status
返回值:成功时为 0,错误时为 -negative
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 命令来请求固件解密给定内存区域中的数据。
参数(输入):struct kvm_sev_dbg
返回值:成功时为 0,错误时为 -negative
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 命令来请求固件加密给定内存区域中的数据。
参数(输入):struct kvm_sev_dbg
返回值:成功时为 0,错误时为 -negative
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 命令注入机密数据。
参数(输入):struct kvm_sev_launch_secret
返回值:成功时为 0,错误时为 -negative
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 命令传递并通过 PEK 签名的访客内存和 VMSA 的 SHA-256 摘要。该命令返回的摘要应与访客所有者与 KVM_SEV_LAUNCH_MEASURE 一起使用的摘要匹配。
如果输入时 len 为零,则度量值 Blob 长度将写入 len,并且 uaddr 未使用。
参数(输入):struct kvm_sev_attestation
返回值:成功时为 0,错误时为 -negative
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,并且不使用所有其他字段。
参数(输入):struct kvm_sev_send_start
返回值:成功时为 0,错误时为 -negative
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,错误时为 -negative
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,错误时为 -negative
14. KVM_SEV_SEND_CANCEL¶
在完成 SEND_START 之后,但在 SEND_FINISH 之前,源 VMM 可以发出 SEND_CANCEL 命令来停止迁移。这是必要的,以便取消的迁移可以在以后使用新的目标重新启动。
返回值:成功时为 0,错误时为 -negative
15. KVM_SEV_RECEIVE_START¶
KVM_SEV_RECEIVE_START 命令用于为传入的 SEV 访客创建内存加密上下文。要创建加密上下文,用户必须提供访客策略、平台公用 Diffie-Hellman (PDH) 密钥和会话信息。
参数:struct kvm_sev_receive_start (in/out)
返回值:成功时为 0,错误时为 -negative
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,错误时为 -negative
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,错误时为 -negative
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,错误时为 -negative
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-fw-abi] 中的 SNP_LAUNCH_START。
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,错误时为 -negative
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-fw-abi] 中的 SNP_LAUNCH_FINISH。
设备属性 API¶
可以使用 /dev/kvm
设备节点上的 KVM_HAS_DEVICE_ATTR
和 KVM_GET_DEVICE_ATTR
ioctl,使用组 KVM_X86_GRP_SEV
来检索 SEV 实现的属性。
当前仅实现了一个属性
KVM_X86_SEV_VMSA_FEATURES
:返回KVM_SEV_INIT2
的vmsa_features
中接受的所有位的集合。
固件管理¶
SEV 访客密钥管理由一个单独的处理器处理,该处理器称为 AMD 安全处理器 (AMD-SP)。在 AMD-SP 内部运行的固件提供了一个安全的密钥管理界面,用于执行常见的虚拟机监控程序活动,例如加密引导代码、快照、迁移和调试访客。有关更多信息,请参阅 SEV 密钥管理规范 [api-spec]
可以使用其自己的非易失性存储来初始化 AMD-SP 固件,或者 OS 可以使用 ccp
模块的参数 init_ex_path
来管理固件的 NV 存储。如果 init_ex_path
指定的文件不存在或无效,则 OS 将使用 PSP 非易失性存储创建或覆盖该文件。
参考¶
有关更多信息,请参阅 [white-paper]、[api-spec]、[amd-apm]、[kvm-forum] 和 [snp-fw-abi]。