安全加密虚拟化 (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];
};

如果虚拟机监控程序不支持在 flagsvmsa_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_initflagsvmsa_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.kodebug_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_updategfn_startuaddrlen 字段将更新,以对应于尚未处理的剩余范围。调用者应继续调用此命令,直到这些字段指示已处理整个范围,例如 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_ATTRKVM_GET_DEVICE_ATTR ioctl,使用组 KVM_X86_GRP_SEV 来检索 SEV 实现的属性。

当前仅实现了一个属性

  • KVM_X86_SEV_VMSA_FEATURES:返回 KVM_SEV_INIT2vmsa_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]