安全加密虛擬化 (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]。