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

如果虚拟机监控程序不支持在 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,虚拟机类型为 KVM_X86_SEV_VM,对于 KVM_SEV_ES_INIT,虚拟机类型为 KVM_X86_SEV_ES_VM

  • struct kvm_sev_initflagsvmsa_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.kodebug_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_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,错误时为 -负数

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_ATTRKVM_GET_DEVICE_ATTR ioctl 来检索,使用组 KVM_X86_GRP_SEV

目前只实现了一个属性

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