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