KVM_CREATE_DEVICE
创建虚机关联的fd,再通过KVM_MEMORY_ENCRYPT_OP
命令字下发命令,所有的虚机管理命令作为KVM_MEMORY_ENCRYPT_OP
命令字的参数,通过id字段的不同来区分,其命令格式如下:struct kvm_sev_cmd {
__u32 id; /* sev cmd ID,取值为sev_cmd_id */
__u64 data; /* 每个sev cmd对应的参数 */
__u32 error;
__u32 sev_fd; /* 字符设备/dev/sev对应的fd */
};
kvm_sev_cmd
结构体中id字段可选的命令集合/* Secure Encrypted Virtualization command */
enum sev_cmd_id {
/* Guest initialization commands */
KVM_SEV_INIT = 0,
KVM_SEV_ES_INIT,
/* Guest launch commands */
KVM_SEV_LAUNCH_START, /* 启动虚机命令,通知PSP,Guest owner与PSP建立会话 */
KVM_SEV_LAUNCH_UPDATE_DATA, /* 更新启动初始化时的虚机内存数据,通知PSP */
KVM_SEV_LAUNCH_UPDATE_VMSA,
KVM_SEV_LAUNCH_SECRET, /* 加载虚机密钥密文到虚机内存,通知PSP进行密钥注入 */
KVM_SEV_LAUNCH_MEASURE, /* 请求PSP计算度量值 */
KVM_SEV_LAUNCH_FINISH,
/* Guest migration commands (outgoing) */
KVM_SEV_SEND_START,
KVM_SEV_SEND_UPDATE_DATA,
KVM_SEV_SEND_UPDATE_VMSA,
KVM_SEV_SEND_FINISH,
/* Guest migration commands (incoming) */
KVM_SEV_RECEIVE_START,
KVM_SEV_RECEIVE_UPDATE_DATA,
KVM_SEV_RECEIVE_UPDATE_VMSA,
KVM_SEV_RECEIVE_FINISH,
/* Guest status and debug commands */
KVM_SEV_GUEST_STATUS,
KVM_SEV_DBG_DECRYPT,
KVM_SEV_DBG_ENCRYPT,
/* Guest certificates commands */
KVM_SEV_CERT_EXPORT,
/* Attestation report */
KVM_SEV_GET_ATTESTATION_REPORT,
/* Guest Migration Extension */
KVM_SEV_SEND_CANCEL,
KVM_SEV_NR_MAX,
};
KVM_SEV_LAUNCH_START
为例,其参数格式如下:struct kvm_sev_launch_start {
__u32 handle;
__u32 policy; /* 启动机密虚机时的策略 */
/* 机密环境准备过程中,涉及到Guest owner与PSP间的数据传输 */
__u64 dh_uaddr;
__u32 dh_len;
__u64 session_uaddr;
__u32 session_len;
};
/**
* struct sev_issue_cmd - SEV ioctl parameters
*
* @cmd: SEV commands to execute
* @opaque: pointer to the command structure
* @error: SEV FW return code on failure
*/
struct sev_issue_cmd {
__u32 cmd; /* In */
__u64 data; /* In */
__u32 error; /* Out */
}
sev_issue_cmd
结构体中cmd字段可选的命令集合/**
* SEV platform commands
*/
enum {
SEV_FACTORY_RESET = 0,
SEV_PLATFORM_STATUS, /* 查询PSP状态 */
/* 请求PSP重新生成PEK签发的所有证书,包括PEK公私钥对,PEK证书,PDH公私钥对,PDH证书,OCA公私钥对,OCA证书 */
SEV_PEK_GEN,
/* 请求PSP生成PEK公钥及其它用户制作PEK证书的信息 */
SEV_PEK_CSR,
/* 请求PSP重新生成PDH,该PDH的公钥信息被PEK私钥签名,由PEK签发 */
SEV_PDH_GEN,
/* 导出PDH公钥及PEK对公钥的签名,用于Gueste owner对PDH的可信验证 */
SEV_PDH_CERT_EXPORT,
/* 往PSP导入PEK证书,声明PSP的Platform owner */
SEV_PEK_CERT_IMPORT,
/* 查询CPU ID,在AMD CA是,此ID将作为CEK下载的输入 */
SEV_GET_ID, /* This command is deprecated, use SEV_GET_ID2 */
SEV_GET_ID2,
SEV_MAX,
};
PLATFORM_STATUS
为例,在下发PLATFORM_STATUS
时会传入结构体sev_user_data_status
,用于接收PSP的输出。/**
* struct sev_user_data_status - PLATFORM_STATUS command parameters
*
* @major: major API version
* @minor: minor API version
* @state: platform state
* @flags: platform config flags
* @build: firmware build id for API version
* @guest_count: number of active guests
*/
struct sev_user_data_status {
/* PSP固件版本号 */
__u8 api_major; /* Out */
__u8 api_minor; /* Out */
/* PSP状态 */
__u8 state; /* Out */
__u32 flags; /* Out */
__u8 build; /* Out */
__u32 guest_count; /* Out */
}
/**
* SevGuestState:
*
* The SevGuestState object is used for creating and managing a SEV
* guest.
*
* # $QEMU \
* -object sev-guest,id=sev0 \
* -machine ...,memory-encryption=sev0
*/
struct SevGuestState {
ConfidentialGuestSupport parent_obj;
/* configuration parameters */
char *sev_device;
uint32_t policy; /* SEV API 中定义的虚机启动策略 */
char *dh_cert_file; /* */
char *session_file;
uint32_t cbitpos;
uint32_t reduced_phys_bits;
bool kernel_hashes;
/* runtime state */
uint32_t handle;
uint8_t api_major;
uint8_t api_minor;
uint8_t build_id;
int sev_fd;
SevState state;
gchar *measurement;
uint32_t reset_cs;
uint32_t reset_ip;
bool reset_data_valid;
};
$ sevctl generate oca.cert oca.key
$ sevctl provision oca.cert oca.key
$ sevctl verify
$ sevctl export --full /opt/sev/cert_chain.cert
cd /opt/
git clone https://gitee.com/anolis/hygon-devkit.git
mv hygon-devkit hygon
cp /opt/hygon/bin/hag /usr/bin
hag general hgsc_import
hag general get_id /* 获取PSP芯片ID */
hag general hgsc_version /* 查询证书版本号 */
hag general hgsc_import -offline -in /path/to/hygon-hgsc-certchain-v1.0-H905P0005040204.bin
hag csv platform_status
hag csv pdh_cert_export
cert_chain.bin
cert_chain.cert
cert_chain_readable.txt
pdh.bin
pdh.cert
pdh_readable.txt
cat cert_chain_readable.txt | grep Userid
Userid: HYGON-SSD-PEK
Userid: HYGON-SSD-OCA
Userid: HYGON-SSD-CEK
cat pdh_readable.txt |grep Userid
Userid: HYGON-SSD-PDH
1. qapi中对sev能力的定义
##
# @SevCapability:
#
# The struct describes capability for a Secure Encrypted
# Virtualization feature.
#
# @pdh: Platform Diffie-Hellman key (base64 encoded)
#
# @cert-chain: PDH certificate chain (base64 encoded)
#
# @cpu0-id: Unique ID of CPU0 (base64 encoded) (since 7.1)
#
# @cbitpos: C-bit location in page table entry
#
# @reduced-phys-bits: Number of physical Address bit reduction when
# SEV is enabled
#
# Since: 2.12
##
{ 'struct': 'SevCapability',
'data': { 'pdh': 'str',
'cert-chain': 'str',
'cpu0-id': 'str',
'cbitpos': 'int',
'reduced-phys-bits': 'int'},
'if': 'TARGET_I386' }
2. 对应的C结构体
struct SevCapability {
char *pdh; /* 用于计算会话密钥PDH证书 */
char *cert_chain; /* 证书链 */
char *cpu0_id;
int64_t cbitpos;
int64_t reduced_phys_bits;
};
3. 具体的查询流程
qmp_query_sev_capabilities
sev_get_capabilities
kvm_vm_ioctl(kvm_state, KVM_MEMORY_ENCRYPT_OP, NULL)
fd = open(DEFAULT_SEV_DEVICE, O_RDWR) /* 打开/dev/sev字符设备 */
sev_get_pdh_info(fd, &pdh_data, &pdh_len, &cert_chain_data, &cert_chain_len, errp)) /* 下发命令字导出证书 */
sev_platform_ioctl(fd, SEV_PDH_CERT_EXPORT, &export, &err)
cap = g_new0(SevCapability, 1);
cap->pdh = g_base64_encode(pdh_data, pdh_len);
cap->cert_chain = g_base64_encode(cert_chain_data, cert_chain_len);
$ sevctl measurement build \
--api-major 01 --api-minor 40 --build-id 40 \
--policy 0x05 \
--tik /path/to/VM_tik.bin \
--firmware /usr/share/edk2/ovmf/OVMF.amdsev.fd \
--kernel /path/to/kernel \
--initrd /path/to/initrd \
--cmdline "my kernel cmdline" \
--vmsa-cpu0 /path/to/vmsa0.bin \
--vmsa-cpu1 /path/to/vmsa1.bin \
--num-cpus 4
--launch-measure-blob /o0nzDKE5XgtVnUZWPhUea/WZYrTKLExR7KCwuMdbActvpWfXTFk21KMZIAAhQny
hag csv generate_launch_blob -help
Usage: generate_launch_blob [options]
Valid options are:
-help Display this summary
-verbose Enable debug log print
-dir dir [optional] Specify the file generation directory
-build ulong Input the build id of the Firmware
-bios infile Input bios file
-kernel infile [optional] Input kernel file
-cmdline infile [optional] Input cmdline file, default is '\0'
-initrd infile [optional] Input initrd file
hag csv generate_launch_blob \
-build 1881 \ /* 固件ID,通过hag csv platform_status查询得到 */
-bios /opt/hygon/csv/OVMF.fd /* 度量值的输入:bios文件 */
ACTIVATE
通知PSP将ASID与内存加密的密钥VEK(VM Encryption Key)关联,PSP的具体逻辑是以ASID为索引,在系统的所有密钥槽中找到对应的VEK,将其加载到内存LAUNCH_UPDATE_DATA
通知PSP对虚机内存进行加密。此时虚机的内存地址空间变为密文。LAUNCH_MEASURE
通知PSP对 ASID关联的内存地址空间内容进行度量值计算,并将加密后的明文返回给Guest ownerBIOS+grub
或OVMF+UEFI+grub
固件启动,但在SEV机密计算场景下,只对OVMF+UEFI+grub
启动方案做了适配。所以在具体启动SEV机密计算虚机,只能以OVMF方式引导虚机;kernel+initrd+commandline
的方式启动虚机;也支持直接以虚机镜像的方式启动虚机。当用户通过前者启动虚机时,需要指定OVMF、kernel和initrd文件,commandline可选,QEMU的传统做法是将OVMF载入虚机内存,将kernel和initrd文件添加到fw_cfg(Firmware Configuration) 设备,然后启动虚机,虚机启动过程中,OVMF逻辑会从fw_cfg设备中读取kernel和initrd文件并加载到内存,准备工作完成之后,跳转到kernel,移交控制权,这里可以看出,PSP对虚机初始化内存的度量,只会包含OVMF固件,并没有kernel和initrd文件,因为kernel和initrd是在虚机运行(即Guest态)之后OVMF从设备加载到内存的。在机密计算场景下,由于Hypervisor是不可信的,静态度量并没有包含kernel和initrd的内容,它完全有可能将用户kernel和initrd文件替换为恶意镜像而不被Guest owner发现,机密计算的方案需要考虑如何处理这种情况。对于使用虚机镜像的方式启动虚机的场景,也存在Hypervisor直接将镜像替换为恶意镜像的可能。我们下面分别讨论这两种场景下机密计算给出的解决方案以及具体的实现流程。-object sev-guest,id=sev0,policy=0x1,cbitpos=47,reduced-phys-bits=5......
-machine pc-i440fx-6.2,memory-encryption=sev0
流程分析
kvm_arch_init
sev_kvm_init /* 初始化SEV上下文 */
/**/
SevGuestState *sev =
(SevGuestState *)object_dynamic_cast(OBJECT(cgs), TYPE_SEV_GUEST);
sev_launch_start
LAUCH_SECRET
API,让安全处理器发起密钥注入。整个过程QEMU处理的是密钥密文。