21. Intel Trust Domain Extensions (TDX)

Intel 的 Trust Domain Extensions (TDX) 透過隔離 guest 暫存器狀態和加密 guest 記憶體來保護機密 guest VM 免受主機和物理攻擊。在 TDX 中,執行在特殊模式下的特殊模組位於主機和 guest 之間,並管理 guest/主機分離。

21.1. TDX 主機核心支援

TDX 引入了一種新的 CPU 模式,稱為安全仲裁模式 (SEAM) 和一個新的隔離範圍,由 SEAM Ranger Register (SEAMRR) 指向。一個 CPU 認證的軟體模組,稱為 “TDX 模組”,執行在新的隔離範圍內,以提供管理和執行受保護 VM 的功能。

TDX 還利用 Intel Multi-Key Total Memory Encryption (MKTME) 為 VM 提供加密保護。TDX 保留部分 MKTME KeyID 作為 TDX 私有 KeyID,這些 KeyID 只能在 SEAM 模式下訪問。BIOS 負責對傳統 MKTME KeyID 和 TDX KeyID 進行分割槽。

在 TDX 模組可用於建立和執行受保護的 VM 之前,必須將其載入到隔離範圍並正確初始化。TDX 架構不要求 BIOS 載入 TDX 模組,但核心假定它由 BIOS 載入。

21.1.1. TDX 啟動時檢測

核心透過在核心啟動期間檢測 TDX 私有 KeyID 來檢測 TDX。下面的 dmesg 顯示了 BIOS 何時啟用了 TDX

[..] virt/tdx: BIOS enabled: private KeyID range: [16, 64)

21.1.2. TDX 模組初始化

核心透過新的 SEAMCALL 指令與 TDX 模組通訊。TDX 模組實現 SEAMCALL 葉函式,以允許核心初始化它。

如果未載入 TDX 模組,則 SEAMCALL 指令會失敗並出現特殊錯誤。在這種情況下,核心會使模組初始化失敗,並報告該模組未載入

[..] virt/tdx: module not loaded

初始化 TDX 模組會消耗大約 ~1/256 的系統 RAM 大小,將其用作 TDX 記憶體的 “元資料”。它還需要額外的 CPU 時間來初始化這些元資料以及 TDX 模組本身。這兩者都不是微不足道的。核心在執行時按需初始化 TDX 模組。

除了初始化 TDX 模組之外,在任何其他 SEAMCALL 可以在該 CPU 上進行之前,必須在一個 CPU 上完成每個 CPU 的初始化 SEAMCALL。

核心提供兩個函式 tdx_enable() 和 tdx_cpu_enable(),以允許 TDX 的使用者啟用 TDX 模組並分別在本地 CPU 上啟用 TDX。

進行 SEAMCALL 需要在該 CPU 上完成 VMXON。目前只有 KVM 實現了 VMXON。目前,tdx_enable() 和 tdx_cpu_enable() 都不會在內部執行 VMXON(並非易事),而是依賴於呼叫者來保證這一點。

要啟用 TDX,TDX 的呼叫者應:1) 暫時停用 CPU 熱插拔;2) 在所有線上 CPU 上執行 VMXON 和 tdx_enable_cpu();3) 呼叫 tdx_enable()。例如

cpus_read_lock();
on_each_cpu(vmxon_and_tdx_cpu_enable());
ret = tdx_enable();
cpus_read_unlock();
if (ret)
        goto no_tdx;
// TDX is ready to use

TDX 的呼叫者必須保證在希望執行任何其他 SEAMCALL 之前,已在任何 CPU 上成功完成 tdx_cpu_enable()。一種典型的用法是在 CPU 熱插拔線上回撥中同時執行 VMXON 和 tdx_cpu_enable(),如果 tdx_cpu_enable() 失敗,則拒絕上線。

使用者可以查閱 dmesg,以檢視是否已初始化 TDX 模組。

如果成功初始化了 TDX 模組,則 dmesg 會顯示如下內容

[..] virt/tdx: 262668 KBs allocated for PAMT
[..] virt/tdx: module initialized

如果 TDX 模組未能初始化,dmesg 也會顯示未能初始化

[..] virt/tdx: module initialization failed ...

21.1.3. TDX 與其他核心元件的互動

21.1.3.1. TDX 記憶體策略

TDX 報告一個 “可轉換記憶體區域” (CMR) 列表,以告訴核心哪些記憶體與 TDX 相容。核心需要構建一個記憶體區域列表(從 CMR 中提取),作為 “TDX 可用” 記憶體,並將這些區域傳遞給 TDX 模組。完成此操作後,這些 “TDX 可用” 記憶體區域將在模組的生命週期內固定。

為了簡化操作,目前核心僅保證頁面分配器中的所有頁面都是 TDX 記憶體。具體來說,核心使用核心 mm 中的所有系統記憶體(在 TDX 模組初始化時)作為 TDX 記憶體,與此同時,拒絕在記憶體熱插拔中上線任何非 TDX 記憶體。

21.1.3.2. 物理記憶體熱插拔

注意,TDX 假定可轉換記憶體在機器執行時始終物理存在。非錯誤的 BIOS 絕不應支援熱移除任何可轉換記憶體。此實現不處理 ACPI 記憶體移除,而是依賴 BIOS 來正確執行。

21.1.3.3. CPU 熱插拔

TDX 模組要求必須在一個 CPU 上完成每個 CPU 的初始化 SEAMCALL,然後才能在該 CPU 上進行任何其他 SEAMCALL。核心提供 tdx_cpu_enable(),以便 TDX 的使用者在想要使用新的 CPU 進行 TDX 任務時執行此操作。

TDX 不支援物理 (ACPI) CPU 熱插拔。在機器啟動期間,TDX 會在啟用 TDX 之前驗證所有啟動時存在的邏輯 CPU 是否與 TDX 相容。非錯誤的 BIOS 絕不應支援物理 CPU 的熱新增/移除。目前,核心不處理物理 CPU 熱插拔,而是依賴 BIOS 來正確執行。

注意,TDX 適用於 CPU 邏輯線上/離線,因此核心仍然允許離線邏輯 CPU 並再次上線。

21.1.3.4. Kexec()

TDX 主機支援目前缺乏處理 kexec 的能力。為了簡單起見,只能在 Kconfig 中啟用其中一個。這將在未來得到修復。

21.1.3.5. 勘誤表

最初幾代的 TDX 硬體存在勘誤。對 TDX 私有記憶體快取行的部分寫入會靜默地 “汙染” 該行。隨後的讀取將消耗該汙染並生成機器檢查。

部分寫入是指小於快取行的寫入事務到達記憶體控制器的記憶體寫入。CPU 透過非臨時寫入指令(如 MOVNTI)或透過 UC/WC 記憶體對映執行這些操作。裝置也可以透過 DMA 執行部分寫入。

理論上,核心錯誤可能會對 TDX 私有記憶體執行部分寫入並觸發意外的機器檢查。更重要的是,機器檢查程式碼會將這些顯示為 “硬體錯誤”,而實際上它們是由軟體觸發的問題。但最終,這個問題很難觸發。

如果平臺存在此類勘誤,核心會在機器檢查處理程式中列印附加訊息,以告知使用者機器檢查可能是由 TDX 私有記憶體上的核心錯誤引起的。

21.1.3.6. 與 S3 和更深狀態的互動

TDX 無法從 S3 和更深的狀態中恢復。當平臺進入 S3 和更深的狀態時,硬體會重置並完全停用 TDX。TDX guest 和 TDX 模組都會被永久銷燬。

核心使用 S3 進行掛起到 RAM,並使用 S4 和更深的狀態進行休眠。目前,為了簡單起見,核心選擇使 TDX 與 S3 和休眠互斥。

當休眠支援可用時,核心會在早期啟動期間停用 TDX

[..] virt/tdx: initialization failed: Hibernation support is enabled

新增 “nohibernate” 核心命令列以停用休眠,以便使用 TDX。

如果啟用了 TDX,則在核心早期啟動期間停用 ACPI S3。使用者需要在 BIOS 中關閉 TDX 才能使用 S3。

21.2. TDX Guest 支援

由於主機無法直接訪問 guest 暫存器或記憶體,因此必須將 hypervisor 的許多正常功能移動到 guest 中。這是透過 guest 核心處理的虛擬化異常 (#VE) 實現的。#VE 完全在 guest 核心內部處理,但有些需要諮詢 hypervisor。

TDX 包括用於從 guest 向 hypervisor 或 TDX 模組進行通訊的新的類似 hypercall 的機制。

21.2.1. 新的 TDX 異常

TDX guest 的行為與裸機和傳統 VMX guest 不同。在 TDX guest 中,其他正常的指令或記憶體訪問可能會導致 #VE 或 #GP 異常。

標有 “*” 的指令有條件地導致異常。下面討論這些指令的詳細資訊。

21.2.1.1. 基於指令的 #VE

  • 埠 I/O (INS, OUTS, IN, OUT)

  • HLT

  • MONITOR, MWAIT

  • WBINVD, INVD

  • VMCALL

  • RDMSR*,WRMSR*

  • CPUID*

21.2.1.2. 基於指令的 #GP

  • 所有 VMX 指令:INVEPT, INVVPID, VMCLEAR, VMFUNC, VMLAUNCH, VMPTRLD, VMPTRST, VMREAD, VMRESUME, VMWRITE, VMXOFF, VMXON

  • ENCLS, ENCLU

  • GETSEC

  • RSM

  • ENQCMD

  • RDMSR*,WRMSR*

21.2.1.3. RDMSR/WRMSR 行為

MSR 訪問行為分為三類

  • 生成 #GP

  • 生成 #VE

  • “Just works”

通常,不應在 guest 中使用 #GP MSR。它們的使用可能表明 guest 中存在錯誤。guest 可能會嘗試使用 hypercall 處理 #GP,但不太可能成功。

#VE MSR 通常可以由 hypervisor 處理。Guest 可以呼叫 hypervisor 來處理 #VE。

“Just works” MSR 不需要任何特殊的 guest 處理。它們可以透過直接將 MSR 傳遞到硬體或透過在 TDX 模組中捕獲和處理來實現。除了可能很慢之外,這些 MSR 看起來與在裸機上執行一樣。

21.2.1.4. CPUID 行為

對於某些 CPUID 葉和子葉,CPUID 返回值(在 guest EAX/EBX/ECX/EDX 中)的虛擬化位欄位可以由 hypervisor 配置。對於這種情況,Intel TDX 模組架構定義了兩種虛擬化型別

  • hypervisor 控制 guest TD 看到的值的位欄位。

  • hypervisor 配置值的位欄位,以便 guest TD 可以看到其本機值或值為 0。對於這些位欄位,hypervisor 可以遮蔽本機值,但不能開啟值。

對於 TDX 模組不知道如何處理的 CPUID 葉和子葉,會生成 #VE。Guest 核心可以使用 hypercall 向 hypervisor 請求該值。

21.2.2. 記憶體訪問上的 #VE

本質上有兩類 TDX 記憶體:私有和共享。私有記憶體接收完整的 TDX 保護。其內容受到保護,無法從 hypervisor 訪問。共享記憶體應在 guest 和 hypervisor 之間共享,並且不會接收完整的 TDX 保護。

TD guest 可以控制其記憶體訪問是被視為私有還是共享。它使用頁面表條目中的一個位來選擇行為。這有助於確保 guest 不會將敏感資訊放在共享記憶體中,從而將其暴露給不受信任的 hypervisor。

21.2.2.1. 共享記憶體上的 #VE

訪問共享對映可能會導致 #VE。hypervisor 最終控制共享記憶體訪問是否會導致 #VE,因此 guest 必須小心,僅引用它可以安全處理 #VE 的共享頁面。例如,guest 應小心,不要在讀取 #VE 資訊結構 (TDG.VP.VEINFO.GET) 之前訪問 #VE 處理程式中的共享記憶體。

共享對映內容完全由 hypervisor 控制。Guest 應該僅將共享對映用於與 hypervisor 通訊。共享對映絕不能用於敏感記憶體內容,如核心堆疊。一個好的經驗法則是,應將 hypervisor 共享記憶體視為與對映到使用者空間的記憶體相同。hypervisor 和使用者空間都完全不受信任。

虛擬裝置的 MMIO 作為共享記憶體實現。Guest 必須小心,不要訪問裝置 MMIO 區域,除非它也準備好處理 #VE。

21.2.2.2. 私有頁面上的 #VE

訪問私有對映也可能導致 #VE。由於所有核心記憶體也是私有記憶體,因此理論上核心可能需要在任意核心記憶體訪問時處理 #VE。這是不可行的,因此 TDX guest 確保在使用核心記憶體之前,所有 guest 記憶體都已 “接受”。

在核心執行之前,韌體會預先接受少量的記憶體(通常為 512M),以確保核心可以啟動而不會受到 #VE 的影響。

允許 hypervisor 單方面將接受的頁面移動到 “阻止” 狀態。但是,如果它這樣做,頁面訪問將不會生成 #VE。相反,它會導致 “TD Exit”,在這種情況下,需要 hypervisor 處理異常。

21.2.3. Linux #VE 處理程式

就像頁面錯誤或 #GP 一樣,#VE 異常可以被處理或致命。通常,未處理的使用者空間 #VE 會導致 SIGSEGV。未處理的核心 #VE 會導致 oops。

在 x86 上處理巢狀異常通常是一件令人討厭的事情。#VE 可能會被 NMI 中斷,NMI 觸發另一個 #VE,從而導致混亂。TDX #VE 架構預料到了這種情況,幷包含一個功能,使其稍微不那麼令人討厭。

在 #VE 處理期間,TDX 模組確保所有中斷(包括 NMI)都被阻止。該阻止一直有效,直到 guest 執行 TDG.VP.VEINFO.GET TDCALL。這允許 guest 控制何時可以傳遞中斷或新的 #VE。

但是,guest 核心仍然必須小心,以避免在此阻止生效時發生潛在的 #VE 觸發操作(如上所述)。當阻止生效時,任何 #VE 都會被提升為雙重錯誤 (#DF),這是不可恢復的。

21.2.4. MMIO 處理

在非 TDX VM 中,MMIO 通常透過允許 guest 訪問一個對映來實現,該對映將在訪問時導致 VMEXIT,然後 hypervisor 模擬該訪問。這在 TDX guest 中是不可能的,因為 VMEXIT 會將暫存器狀態暴露給主機。TDX guest 不信任主機,並且不能將其狀態暴露給主機。

在 TDX 中,MMIO 區域通常會在 guest 中觸發 #VE 異常。然後,guest #VE 處理程式會在 guest 內部模擬 MMIO 指令,並將其轉換為對主機的受控 TDCALL,而不是將 guest 狀態暴露給主機。

x86 上的 MMIO 地址只是特殊的物理地址。理論上,可以使用訪問記憶體的任何指令來訪問它們。但是,核心指令解碼方法受到限制。它僅設計用於解碼由 io.h 宏生成的指令。

透過其他方式(如結構覆蓋)訪問 MMIO 可能會導致 oops。

21.2.5. 共享記憶體轉換

所有 TDX guest 記憶體在啟動時都以私有方式啟動。hypervisor 無法訪問此記憶體。但是,某些核心使用者(如裝置驅動程式)可能需要與 hypervisor 共享資料。為此,必須在共享和私有之間轉換記憶體。這可以使用一些現有的記憶體加密助手來完成

  • set_memory_decrypted() 將一系列頁面轉換為共享。

  • set_memory_encrypted() 將記憶體轉換回私有。

裝置驅動程式是共享記憶體的主要使用者,但無需觸及每個驅動程式。DMA 緩衝區和 ioremap() 會自動執行轉換。

TDX 對大多數 DMA 分配使用 SWIOTLB。SWIOTLB 緩衝區在啟動時轉換為共享。

對於一致的 DMA 分配,DMA 緩衝區在分配時進行轉換。有關詳細資訊,請檢視 force_dma_unencrypted()。

21.3. 證明

證明用於在向 guest 提供機密之前,向其他實體驗證 TDX guest 的可信度。例如,金鑰伺服器可能希望使用證明來驗證 guest 是否是所需的 guest,然後才釋放加密金鑰以掛載加密的 rootfs 或輔助驅動器。

TDX 模組使用構建時度量暫存器 (MRTD) 和執行時度量暫存器 (RTMR) 記錄 guest 啟動過程各個階段的 TDX guest 狀態。與 guest 初始配置和韌體映像相關的度量記錄在 MRTD 暫存器中。與初始狀態、核心映像、韌體映像、命令列選項、initrd、ACPI 表等相關的度量記錄在 RTMR 暫存器中。有關更多詳細資訊,例如,請參閱 TDX 虛擬韌體設計規範,標題為 “TD 度量” 的部分。在 TDX guest 執行時,證明過程用於證明這些度量。

證明過程包括兩個步驟:TDREPORT 生成和 Quote 生成。

TDX guest 使用 TDCALL[TDG.MR.REPORT] 從 TDX 模組獲取 TDREPORT (TDREPORT_STRUCT)。TDREPORT 是由 TDX 模組生成的固定大小的資料結構,其中包含 guest 特定資訊(如構建和啟動度量)、平臺安全版本以及用於保護 TDREPORT 完整性的 MAC。使用者提供的 64 位元組 REPORTDATA 用作輸入幷包含在 TDREPORT 中。通常,它可以是證明服務提供的一些 nonce,以便可以唯一地驗證 TDREPORT。有關 TDREPORT 的更多詳細資訊,請參見 Intel TDX 模組規範,標題為 “TDG.MR.REPORT Leaf” 的部分。

獲得 TDREPORT 後,證明過程的第二步是將其傳送到 Quoting Enclave (QE) 以生成 Quote。根據設計,TDREPORT 只能在本地平臺上驗證,因為 MAC 金鑰繫結到平臺。為了支援 TDREPORT 的遠端驗證,TDX 利用 Intel SGX Quoting Enclave 在本地驗證 TDREPORT 並將其轉換為可遠端驗證的 Quote。將 TDREPORT 傳送到 QE 的方法是特定於實現的。證明軟體可以選擇任何可用的通訊通道(即 vsock 或 TCP/IP)將 TDREPORT 傳送到 QE 並接收 Quote。

21.4. 參考

TDX 參考資料在此處收集

https://www.intel.com/content/www/us/en/developer/articles/technical/intel-trust-domain-extensions.html