32. 軟體保護擴充套件 (SGX)¶
32.1. 概述¶
軟體保護擴充套件 (SGX) 硬體使使用者空間應用程式能夠預留私有程式碼和資料記憶體區域
特權(環 0)ENCLS 函式協調區域的構造。
非特權(環 3)ENCLU 函式允許應用程式進入並在區域內執行。
這些記憶體區域稱為飛地。飛地只能在固定的入口點進入。每個入口點一次只能容納一個硬體執行緒。雖然飛地透過使用 ENCLS 函式從常規二進位制檔案載入,但只有飛地內的執行緒才能訪問其記憶體。該區域被 CPU 拒絕從外部訪問,並在離開 LLC 之前進行加密。
可以透過以下方式確定支援:
grep sgx /proc/cpuinfo
SGX 必須同時在處理器中受支援,並且由 BIOS 啟用。如果 SGX 在具有硬體支援的系統上顯示為不受支援,請確保在 BIOS 中啟用了支援。如果 BIOS 在 SGX 的“已啟用”和“軟體已啟用”模式之間提供選擇,請選擇“已啟用”。
32.2. 飛地頁面快取¶
SGX 使用飛地頁面快取 (EPC) 來儲存與飛地關聯的頁面。它包含在物理記憶體的 BIOS 保留區域中。與用於常規記憶體的頁面不同,頁面只能在飛地構建期間透過特殊的、有限的 SGX 指令從飛地外部訪問。
只有在飛地內執行的 CPU 才能直接訪問飛地記憶體。但是,在飛地內執行的 CPU 可能會訪問飛地外的正常記憶體。
核心管理飛地記憶體的方式與處理裝置記憶體的方式類似。
32.2.1. 飛地頁面型別¶
- SGX 飛地控制結構 (SECS)
飛地的地址範圍、屬性和其他全域性資料由此結構定義。
- 常規 (REG)
常規 EPC 頁面包含飛地的程式碼和資料。
- 執行緒控制結構 (TCS)
執行緒控制結構頁面定義了飛地的入口點並跟蹤飛地執行緒的執行狀態。
- 版本陣列 (VA)
版本陣列頁面包含 512 個槽,每個槽都可以包含從 EPC 中逐出的頁面的版本號。
32.2.2. 飛地頁面快取對映¶
處理器在稱為飛地頁面快取對映 (EPCM) 的硬體元資料結構中跟蹤 EPC 頁面。EPCM 包含每個 EPC 頁面的條目,其中描述了擁有飛地、訪問許可權和頁面型別等。
EPCM 許可權與正常頁表分開。這可以防止核心例如允許寫入飛地希望保持只讀的資料。EPCM 許可權可能只會對正常的 x86 頁面許可權施加額外的限制。
對於所有意圖和目的,SGX 架構允許處理器隨意使所有 EPCM 條目無效。這要求軟體隨時準備好處理 EPCM 故障。實際上,這可能會發生在電源轉換等事件中,此時加密飛地記憶體的臨時金鑰會丟失。
32.3. 應用程式介面¶
32.3.1. 飛地構建函式¶
除了傳統的編譯器和連結器構建過程之外,SGX 還有一個單獨的飛地“構建”過程。飛地必須先構建才能執行(進入)。構建飛地的第一步是開啟 /dev/sgx_enclave 裝置。由於飛地記憶體受到直接訪問的保護,因此使用特殊的特權指令將資料複製到飛地頁面並建立飛地頁面許可權。
-
long sgx_ioc_enclave_create(struct sgx_encl *encl, void __user *arg)¶
SGX_IOC_ENCLAVE_CREATE的處理程式
引數
struct sgx_encl *encl飛地指標。
void __user *argioctl 引數。
描述
為飛地分配核心資料結構並呼叫 ECREATE。
返回
0:成功。
-EIO:ECREATE 失敗。
-errno:POSIX 錯誤。
-
long sgx_ioc_enclave_add_pages(struct sgx_encl *encl, void __user *arg)¶
SGX_IOC_ENCLAVE_ADD_PAGES的處理程式
引數
struct sgx_encl *encl飛地指標
void __user *arg指向 struct sgx_enclave_add_pages 例項的使用者指標
描述
將一個或多個頁面新增到未初始化的飛地,並可選擇使用頁面的內容擴充套件度量。SECINFO 和度量掩碼應用於所有頁面。
TCS 的 SECINFO 必須始終包含零許可權,因為 CPU 會以靜默方式將其歸零。允許其他任何許可權都會導致度量不匹配。
mmap() 的保護位受頁面許可權的限制。對於每個頁面地址,使用以下啟發式方法計算最大保護位:
常規頁面:PROT_R、PROT_W 和 PROT_X 匹配 SECINFO 許可權。
TCS 頁面:PROT_R | PROT_W。
不允許 mmap() 超出給定地址範圍內的最大保護位的最小值。
該函式取消初始化飛地的核心資料結構,並在以下任何情況下返回 -EIO:
飛地頁面快取 (EPC),即儲存飛地的物理記憶體,已被無效化。這將導致 EADD 和 EEXTEND 失敗。
如果執行 EADD 時源地址以某種方式損壞。
返回
0:成功。
-EACCES:源頁面位於 noexec 分割槽中。
-ENOMEM:EPC 頁面不足。
-EINTR:在處理資料之前呼叫被中斷。
- -EIO:由於無效的源地址導致 EADD 或 EEXTEND 失敗
或斷電。
-errno:POSIX 錯誤。
-
long sgx_ioc_enclave_init(struct sgx_encl *encl, void __user *arg)¶
SGX_IOC_ENCLAVE_INIT的處理程式
引數
struct sgx_encl *encl飛地指標
void __user *arg指向 struct sgx_enclave_init 例項的使用者空間指標
描述
重新整理任何未完成的排隊 EADD 操作並執行 EINIT。根據需要重寫啟動飛地公鑰雜湊 MSR,以匹配從提供的 sigstruct 計算出的飛地的 MRSIGNER。
返回
0:成功。
-EPERM:無效的 SIGSTRUCT。
-EIO:由於斷電導致 EINIT 失敗。
-errno:POSIX 錯誤。
-
long sgx_ioc_enclave_provision(struct sgx_encl *encl, void __user *arg)¶
SGX_IOC_ENCLAVE_PROVISION的處理程式
引數
struct sgx_encl *encl飛地指標
void __user *arg指向 struct sgx_enclave_provision 例項的使用者空間指標
描述
透過為 /dev/sgx_provision 提供檔案控制代碼,允許飛地的 ATTRIBUTE.PROVISION_KEY。
返回
0:成功。
-errno:否則。
32.3.2. 飛地執行時管理¶
支援 SGX2 的系統還支援對已初始化飛地的更改:修改飛地頁面許可權和型別,以及動態新增和刪除飛地頁面。當飛地訪問其地址範圍內沒有後備頁面的地址時,將動態地向飛地新增新的常規頁面。仍然需要飛地在新頁面上執行 EACCEPT 才能使用它。
-
long sgx_ioc_enclave_restrict_permissions(struct sgx_encl *encl, void __user *arg)¶
SGX_IOC_ENCLAVE_RESTRICT_PERMISSIONS的處理程式
引數
struct sgx_encl *encl飛地指標
void __user *arg指向
struct sgx_enclave_restrict_permissions例項的使用者空間指標
描述
SGX2 區分放寬和限制由已初始化飛地(在 SGX_IOC_ENCLAVE_INIT 之後)擁有的頁面的硬體(EPCM 許可權)維護的飛地頁面許可權。
無法從飛地內限制 EPCM 許可權,飛地需要核心執行特權級別 0 指令 ENCLS[EMODPR] 和 ENCLS[ETRACK]。硬體將忽略嘗試使用此呼叫來放寬 EPCM 許可權。
返回
0:成功
-errno:否則
-
long sgx_ioc_enclave_modify_types(struct sgx_encl *encl, void __user *arg)¶
SGX_IOC_ENCLAVE_MODIFY_TYPES的處理程式
引數
struct sgx_encl *encl飛地指標
void __user *arg指向
struct sgx_enclave_modify_types例項的使用者空間指標
描述
更改飛地頁面型別的功能支援以下用例:
可以透過將常規頁面 (
SGX_PAGE_TYPE_REG) 的型別更改為 TCS (SGX_PAGE_TYPE_TCS) 頁面來將 TCS 頁面新增到飛地。透過此支援,可以動態增加已初始化飛地支援的執行緒數。可以透過將頁面型別更改為
SGX_PAGE_TYPE_TRIM,從已初始化的飛地動態刪除常規頁面或 TCS 頁面。將頁面型別更改為SGX_PAGE_TYPE_TRIM會將頁面標記為刪除,實際刪除由執行 ENCLU[EACCEPT] 後呼叫的 ioctl() 的SGX_IOC_ENCLAVE_REMOVE_PAGES處理程式完成,從飛地內的SGX_PAGE_TYPE_TRIM頁面呼叫。
返回
0:成功
-errno:否則
-
long sgx_ioc_enclave_remove_pages(struct sgx_encl *encl, void __user *arg)¶
SGX_IOC_ENCLAVE_REMOVE_PAGES的處理程式
引數
struct sgx_encl *encl飛地指標
void __user *arg指向
struct sgx_enclave_remove_pages例項的使用者空間指標
描述
從已初始化的飛地中刪除頁面的流程的最後一步。完整流程為:
使用者使用
SGX_IOC_ENCLAVE_MODIFY_TYPESioctl() 將要刪除的頁面的型別更改為SGX_PAGE_TYPE_TRIM。使用者透過從飛地內執行 ENCLU[EACCEPT] 來批准頁面刪除。
使用者使用此處處理的
SGX_IOC_ENCLAVE_REMOVE_PAGESioctl() 啟動實際頁面刪除。
首先刪除指向該頁面的任何頁表條目,然後繼續實際刪除飛地頁面和支援它的資料。
VA 頁面不受此刪除的影響。因此,飛地最終可能比支援其所有頁面所需的 VA 頁面更多。
返回
0:成功
-errno:否則
32.3.3. 飛地 vDSO¶
只能透過特定於 SGX 的 EENTER 和 ERESUME 函式進入飛地,這是一個非常複雜的過程。由於轉換到飛地和從飛地轉換的複雜性,飛地通常使用庫來處理實際轉換。這大致類似於大多數應用程式如何使用 glibc 實現來包裝系統呼叫。
飛地的另一個關鍵特性是,它們可以生成異常作為其正常操作的一部分,這些異常需要在飛地中處理或對於 SGX 是唯一的。
SGX 可以利用 vDSO 提供的特殊異常修復,而不是使用傳統的訊號機制來處理這些異常。核心提供的 vDSO 函式包裝了到/從飛地的低階轉換,如 EENTER 和 ERESUME。vDSO 函式攔截原本會生成訊號的異常,並將故障資訊直接返回給其呼叫者。這避免了處理訊號處理程式的需要。
-
vdso_sgx_enter_enclave_t¶
Typedef:__vdso_sgx_enter_enclave() 的原型,一個用於進入 SGX 飛地的 vDSO 函式。
語法
int vdso_sgx_enter_enclave_t (unsigned long rdi, unsigned long rsi, unsigned long rdx, unsigned int function, unsigned long r8, unsigned long r9, struct sgx_enclave_run *run)
引數
unsigned long rdiRDI 的直通值
unsigned long rsiRSI 的直通值
unsigned long rdxRDX 的直通值
unsigned int functionENCLU 函式,必須是 EENTER 或 ERESUME
unsigned long r8R8 的直通值
unsigned long r9R9 的直通值
struct sgx_enclave_run *runstruct sgx_enclave_run,必須為非 NULL
注意
__vdso_sgx_enter_enclave() 不確保完全符合 x86-64 ABI,例如不處理 XSAVE 狀態。除了非易失性通用暫存器、EFLAGS.DF 和 RSP 對齊之外,根據 x86-64 ABI 儲存/設定狀態是飛地及其執行時的責任,即,在飛地及其執行時都經過仔細考慮後,才能從 C 程式碼呼叫 __vdso_sgx_enter_enclave()。
描述
除了 RAX、RBX 和 RCX 之外的所有通用暫存器都按原樣傳遞到飛地。RAX、RBX 和 RCX 由 EENTER 和 ERESUME 使用,並分別載入 function、非同步退出指標和 run.tcs。
RBP 和堆疊用於將 __vdso_sgx_enter_enclave() 錨定到飛地前狀態,例如,在飛地退出後檢索 run.exception 和 run.user_handler。所有其他暫存器都可供飛地及其執行時使用,例如,飛地可以將其他資料推送到堆疊上(並修改 RSP)以將資訊傳遞到可選的使用者處理程式(請參見下文)。
ENCLU 上報告的大多數異常,包括在飛地內發生的異常,都將被修復並同步報告,而不是透過標準訊號傳遞。除錯異常 (#DB) 和斷點 (#BP) 永遠不會被修復,並且始終透過標準訊號傳遞。在同步報告的異常中,將返回 -EFAULT,並且有關異常的詳細資訊將記錄在 run.exception(可選的 sgx_enclave_exception 結構)中。
返回
0:ENCLU 函式已成功執行。
-EINVAL:無效的 ENCL 編號(既不是 EENTER 也不是 ERESUME)。
32.4. ksgxd¶
SGX 支援包括一個名為 ksgxd 的核心執行緒。
32.4.1. EPC 清理¶
ksgxd 在 SGX 初始化時啟動。飛地記憶體通常在處理器啟動或重置時可以使用。但是,如果自重置以來一直在使用 SGX,則飛地頁面可能處於不一致的狀態。例如,這可能會在崩潰和 kexec() 週期後發生。在啟動時,ksgxd 重新初始化所有飛地頁面,以便可以分配和重新使用它們。
清理是透過遍歷 EPC 地址空間並將 EREMOVE 函式應用於每個物理頁面來完成的。某些飛地頁面(如 SECS 頁面)在其他頁面上具有硬體依賴關係,這會阻止 EREMOVE 起作用。執行兩次 EREMOVE 傳遞會刪除依賴關係。
32.4.2. 頁面回收器¶
與核心 kswapd 類似,ksgxd 負責管理飛地記憶體的過度提交。如果系統耗盡飛地記憶體,則 ksgxd 會將飛地記憶體“交換”到普通記憶體。
32.5. 啟動控制¶
SGX 提供了一種啟動控制機制。複製完所有飛地頁面後,核心執行 EINIT 函式,該函式初始化飛地。只有在此之後,CPU 才能在飛地內執行。
EINIT 函式採用飛地度量的 RSA-3072 簽名。該函式檢查度量是否正確,並且簽名是否使用雜湊到表示公鑰 SHA256 的四個 IA32_SGXLEPUBKEYHASH{0, 1, 2, 3} MSR 的金鑰簽名。
這些 MSR 可以由 BIOS 配置為可讀或可寫。Linux 僅支援可寫配置,以便核心完全控制啟動控制策略。在呼叫 EINIT 函式之前,驅動程式設定 MSR 以匹配飛地的簽名金鑰。
32.6. 加密引擎¶
為了在飛地資料不在 CPU 封裝中時隱藏飛地資料,記憶體控制器具有加密引擎來透明地加密和解密飛地記憶體。
在 Ice Lake 之前的 CPU 中,記憶體加密引擎 (MEE) 用於加密離開 CPU 快取的頁面。MEE 使用具有 SRAM 中根的 n 元 Merkle 樹來維護加密資料的完整性。這提供了完整性和防重放保護,但無法擴充套件到大型記憶體大小,因為更新 Merkle 樹所需的時間與記憶體大小呈對數增長。
從 Icelake 開始的 CPU 使用完全記憶體加密 (TME) 代替 MEE。基於 TME 的 SGX 實現沒有完整性 Merkle 樹,這意味著完整性和重放攻擊不會得到緩解。但是,它包括額外的更改,以防止返回密文和建立 SW 記憶體別名。
透過 MEE 和 TME 系統上的範圍暫存器阻止 DMA 到飛地記憶體 (SDM section 41.10)。
32.7. 使用模型¶
32.7.2. 應用程式容器¶
應用程式可以載入到容器飛地中,該容器飛地專門配置了庫作業系統和允許應用程式執行的執行時。當執行緒進入飛地時,飛地執行時和庫作業系統協同工作以執行應用程式。
32.8. 潛在核心 SGX 錯誤的影響¶
32.8.1. EPC 洩漏¶
當 EPC 頁面洩漏發生時,dmesg 中會顯示如下 WARNING:
“EREMOVE 返回 … 並且 EPC 頁面洩漏。SGX 可能變得無法使用……”
這實際上是 EPC 頁面的核心釋放後使用,並且由於 SGX 的工作方式,在釋放時檢測到該錯誤。核心不會將頁面添加回可用的 EPC 頁面池,而是故意洩漏該頁面,以避免將來出現其他錯誤。
發生這種情況時,核心可能會很快洩漏更多 EPC 頁面,並且 SGX 可能會變得無法使用,因為 SGX 可用的記憶體有限。但是,雖然這對於 SGX 可能是致命的,但核心的其餘部分不太可能受到影響,並且應該繼續工作。
因此,發生這種情況時,使用者應停止執行任何新的 SGX 工作負載(或只是任何新的工作負載),並遷移所有有價值的工作負載。雖然機器重啟可以恢復所有 EPC 記憶體,但應將該錯誤報告給 Linux 開發人員。
32.9. 虛擬 EPC¶
該實現還有一個虛擬 EPC 驅動程式來支援訪客中的 SGX 飛地。與 SGX 驅動程式不同,由虛擬 EPC 驅動程式分配的 EPC 頁面沒有與其關聯的特定飛地。這是因為 KVM 不跟蹤訪客如何使用 EPC 頁面。
因此,SGX 核心頁面回收器不支援透過虛擬 EPC 驅動程式回收分配給 KVM 訪客的 EPC 頁面。如果使用者想要在同一臺機器上的主機和訪客中部署 SGX 應用程式,則使用者應為主機 SGX 應用程式保留足夠的 EPC(透過從物理 EPC 大小中減去所有 SGX VM 的總虛擬 EPC 大小),以便它們可以以可接受的效能執行。
體系結構行為是在訪客重啟後也將所有 EPC 頁面恢復為未初始化的狀態。由於只能透過特權 ENCLS[EREMOVE] 指令達到此狀態,因此 /dev/sgx_vepc 提供了 SGX_IOC_VEPC_REMOVE_ALL ioctl 以在虛擬 EPC 中的所有頁面上執行該指令。
EREMOVE 可能因三個原因而失敗。使用者空間必須注意預期的失敗並按以下方式處理它們:
當任何執行緒在頁面所屬的飛地中執行時,頁面刪除將始終失敗。在這種情況下,ioctl 將返回
EBUSY,而不管它是否已成功刪除某些頁面;使用者空間可以透過阻止對映虛擬 EPC 的任何 vcpu 的執行來避免這些失敗。如果同時為引用同一“SECS”元資料頁面的頁面呼叫兩次
EREMOVE,則頁面刪除將導致一般保護錯誤。如果同時呼叫SGX_IOC_VEPC_REMOVE_ALL,或者在關閉訪客中的/dev/sgx_vepc檔案描述符的同時呼叫SGX_IOC_VEPC_REMOVE_ALL,則可能會發生這種情況;它也將報告為EBUSY。可以透過序列化對 ioctl() 和 close() 的呼叫來避免使用者空間中的這種情況,但通常來說,這不應該是一個問題。最後,如果SECS元資料頁面仍有子頁面,則頁面移除將失敗。可以透過在所有對映到客戶機的
/dev/sgx_vepc檔案描述符上執行SGX_IOC_VEPC_REMOVE_ALL來移除子頁面。這意味著必須呼叫兩次 ioctl():一組初始呼叫用於移除子頁面,然後一組後續呼叫用於移除 SECS 頁面。第二組呼叫僅對於那些從第一次呼叫返回非零值的對映是必需的。如果第二輪SGX_IOC_VEPC_REMOVE_ALL呼叫中任何一個的返回程式碼不是 0,則表示核心或使用者空間客戶端存在錯誤。