AVF 架構

Android 提供實作 Android 虛擬化架構所需的所有元件的參考實作項目。目前這項實作僅限於 ARM64。本頁面說明架構架構。

背景

Arm 架構最多可支援四個例外狀況層級,其中例外狀況層級 0 (EL0) 的權限最低,而例外狀況層級 3 (EL3) 的權限最高。Android 程式碼集 (所有使用者空間元件) 的大部分會在 EL0 執行。其餘的通常稱為「Android」的部分是 Linux 核心,會在 EL1 上執行。

EL2 層可讓您導入管理程序,將記憶體和裝置隔離在 EL1/EL0 的個別 pVM,同時具有高度的機密性和完整性保證。

管理程序

以核心為基礎的受保護虛擬機器 (pKVM) 是建構在 Linux KVM 管理程序之上,可在建立時限制對在標示為「受保護」的訪客虛擬機器中執行的酬載存取權。

KVM/arm64 支援不同的執行模式,取決於特定 CPU 功能 (即虛擬化主機擴充功能 (VHE),ARMv8.1 以上版本) 的可用性。在這些模式中 (通常稱為非 VHE 模式) 中,管理程序程式碼會在啟動期間從核心映像檔中分離出來並安裝在 EL2,而核心本身是在 EL1 執行。雖然 KVM 的 EL2 元件是 Linux 程式碼庫的一部分,但它是負責在多個 EL1 之間切換的小型元件。管理程序元件是透過 Linux 編譯,但位於 vmlinux 映像檔的個別專屬記憶體區段中。利用這項設計,pKVM 會利用新功能擴充管理程序程式碼,對 Android 主機核心和使用者空間設下限制,並限制主機對訪客記憶體和管理程序的存取權。

pKVM 供應商模組

pKVM 供應商模組是硬體專屬模組,內含裝置專屬功能,例如輸入輸出記憶體管理單位 (IOMMU) 驅動程式。這些模組可讓您將需要例外層級 2 (EL2) 存取權的安全性功能移植至 pKVM。

如要瞭解如何導入及載入 pKVM 供應商模組,請參閱「導入 pKVM 供應商模組」一文。

啟動程序

下圖顯示 pKVM 的啟動程序:

pKVM 啟動程序

圖 1. pKVM 啟動程序

  1. 系統啟動載入程式進入 EL2 的一般核心。
  2. 一般核心會偵測其是在 EL2 執行,並將自身降級為 EL1,而 pKVM 及其模組會繼續在 EL2 執行。此外,系統目前會載入 pKVM 供應商模組。
  3. 一般核心會繼續正常啟動,載入所有必要的裝置驅動程式,直到到達使用者空間為止。此時,pKVM 已就位,並處理第 2 階段的頁面表。

啟動程序只會信任系統啟動載入程式,在早期啟動期間維持核心映像檔的完整性。當核心權限遭到解除時,管理程序就不會再信任核心,因此即使核心遭到入侵,管理程序也必須負責保護自己。

將 Android 核心和輔助作業系統放在同一個二進位映像檔中,可讓兩者之間的通訊介面緊密結合。這種緊密耦合機制可保證兩個元件的不可分割更新,讓介面無須保持穩定,並提供大量彈性,而不會犧牲長期維護能力。當兩個元件能夠合作,且不會影響管理程序提供的安全保證時,緊密連結也能讓效能達到最佳化。

此外,在 Android 生態系統中採用 GKI 後,系統會自動允許將 pKVM 虛擬機器管理程序部署至與核心相同的二進位檔 Android 裝置。

CPU 記憶體存取權保護

Arm 架構會指定記憶體管理單元 (MMU) 分成兩個獨立階段,兩者皆可用於實作位址轉譯,以及對記憶體的不同部分實施存取控制。階段 1 MMU 由 EL1 控管,且允許第一級地址轉譯。Linux 會使用第 1 階段 MMU 來管理提供給每個使用者空間程序的虛擬位址空間,以及其自己的虛擬位址空間。

階段 2 MMU 由 EL2 控制,可在階段 1 MMU 的輸出位址上套用第二個位址轉譯,產生實體位址 (PA)。管理程序可以使用階段 2 轉譯,控制及翻譯所有訪客 VM 的記憶體存取行為。如圖 2 所示,啟用兩個轉譯階段時,第 1 階段的輸出位址稱為中介實體位址 (IPA)。注意:虛擬位址 (VA) 會轉譯為 IPA,然後轉譯為 PA。

CPU 記憶體存取權保護措施

圖 2. CPU 記憶體存取權保護措施

過去 KVM 執行時會在執行訪客動作時啟用階段 2 翻譯,而在執行主機 Linux 核心時,階段 2 則會停用。此架構允許從主機階段 1 MMU 存取記憶體,通過階段 2 MMU,因此允許從主機無限制地存取訪客記憶體頁面。另一方面,pKVM 即使在主機環境中也能啟用第 2 階段保護機制,並讓管理程序負責保護來賓記憶體頁面,而非主機。

KVM 會在第 2 階段充分運用位址轉譯,為訪客實作複雜的 IPA/PA 對應,讓訪客誤以為記憶體是連續的,即使實際上有碎裂情形也一樣。不過,主機的階段 2 MMU 僅限使用存取權控管。主機階段 2 會進行身分對應,確保主機 IPA 空間中的連續記憶體在 PA 空間中也是連續的。這個架構可在頁面快照中使用大型對應項目,進而降低對轉譯暫存緩衝區 (TLB) 的壓力。由於身分對應可由 PA 編入索引,主機階段 2 也會用於直接在頁面表格中追蹤頁面擁有權。

直接記憶體存取 (DMA) 保護

如先前所述,在 CPU 頁面表格中取消對 Linux 主機的訪客頁面對應,雖然是保護訪客記憶體的必要步驟,但仍不足以達到保護效果。pKVM 還需要防範主機核心控制下具 DMA 功能的裝置所進行的記憶體存取,以及惡意主機發起的 DMA 攻擊。為了防止這類裝置存取訪客記憶體,pKVM 需要系統中每個支援 DMA 裝置的輸入/輸出記憶體管理單位 (IOMMU) 硬體,如圖 3 所示。

Dma 記憶體存取防護

圖 3. DMA 記憶體存取保護

至少需提供 IOMMU 硬體,才能在頁面精細程度上授予及撤銷裝置對實體記憶體的讀取/寫入權限。不過,由於 IOMMU 硬體會假設 ID 對應階段 2,因此會限制在 pVM 中使用裝置。

為確保虛擬機器之間的隔離,IOMMU 必須能區分不同實體所產生的記憶體交易,以便使用適當的頁面表來進行轉譯。

此外,減少 EL2 的 SoC 專屬程式碼數量,是降低 pKVM 的整體信任運算基礎 (TCB) 的關鍵策略,並與在超級虛擬機中納入 IOMMU 驅動程式相反。為緩解此問題,EL1 的主機負責執行輔助 IOMMU 管理工作,例如電源管理、初始化,以及在適當情況下中斷處理。

不過,將主機控制裝置狀態,對 IOMMU 硬體的程式設計介面設有額外要求,確保無法透過其他方式規避權限檢查 (例如在裝置重設後)。

Arm 系統記憶體管理單元 (SMMU) 架構是 Arm 裝置的標準 IOMMU,可同時提供隔離和直接指派功能。這是建議的參考解決方案架構。

記憶體擁有權

啟動時,系統會假設所有非管理程序記憶體都由主機擁有,並由管理程序加以追蹤。產生 pVM 時,主機會提供記憶體頁面以允許其啟動,且管理程序會將這些頁面的擁有權從主機轉移至 pVM。因此,管理程序會在主機的階段 2 頁面表格中設定存取權控管限制,避免該頁面再次存取頁面,向訪客提供機密性。

主機和訪客之間的通訊是藉由受控管的記憶體共用而完成。訪客可使用超呼叫將部分頁面傳回主機,指示管理程序將主機階段 2 頁表格中的這些頁面重新對應。同樣地,主機與 TrustZone 的通訊功能可透過記憶體共用和/或借用作業實現,而這些作業都會受到 pKVM 密切監控和控制,並採用 Arm 適用的韌體架構 (FF-A) 規格

由於 pVM 的記憶體需求可能會隨時間變化,因此系統會提供超呼叫,允許屬於呼叫端的指定頁面重新排回主機。實際上,這個超級呼叫會與 virtio 氣球通訊協定搭配使用,讓 VMM 向 pVM 要求記憶體,並讓 pVM 以受控方式通知 VMM 已釋放的頁面。

管理程序負責追蹤系統中所有記憶體頁面的擁有權,以及這些頁面是否與其他實體共用或出借。這類狀態追蹤作業多半是透過附加至主機和訪客階段 2 頁面表格的中繼資料,以及在頁面表格項目 (PTE) 中使用保留位元來完成,這些位元顧名思義,是保留給軟體使用的。

主機必須確保不會嘗試存取已遭虛擬機器人封鎖的網頁。非法的主機存取會導致管理程序將同步例外狀況插入主機,進而導致負責任的使用者空間工作接收 SEGV 信號,或是主機核心停止運作。為避免意外存取,主機核心會將提供給訪客的網頁設為不符合換出或合併的資格。

中斷處理和計時器

中斷是訪客與裝置互動及 CPU 之間通訊的重要部分,其中處理器間中斷 (IPI) 是主要的通訊機制。KVM 模型會將所有虛擬中斷管理工作委派給 EL1 主機,而該主機會以管理程序中不受信任的部分運作。

pKVM 提供完整的通用中斷控制器第 3 版 (GICv3) 模擬功能,以現有的 KVM 程式碼為基礎。計時器和 IPI 會在這個不受信任的模擬程式碼中處理。

GICv3 支援

EL1 和 EL2 之間的介面必須確保 EL1 主機看得到完整的中斷狀態,包括與中斷相關的管理程序註冊副本。這類可視性通常是透過共用記憶體區域達成,每個虛擬 CPU (vCPU) 一個。

系統登錄器執行階段支援程式碼可簡化為僅支援軟體產生的中斷登錄器 (SGIR) 和停用中斷登錄器 (DIR) 登錄器攔截。架構規定這些暫存器一律會陷至 EL2,而其他陷阱到目前為止只用於減輕勘誤。其他所有事物都由硬體處理。

在 MMIO 方面,所有內容都會在 EL1 模擬,並重複使用 KVM 中的所有現有基礎架構。最後,Wait for Interrupt (WFI) 一律會被轉發到 EL1,因為這是 KVM 使用的其中一項基本排程基本功能。

支援計時器

虛擬計時器的比較子值必須在每個附加的 WFI 上,透過 EL1 公開,這樣 EL1 才能插入計時器,在 vCPU 遭封鎖時中斷。實體計時器完全模擬,且所有陷阱都會轉發至 EL1。

MMIO 處理

如要與虛擬機器監視器 (VMM) 通訊並執行 GIC 模擬,MMIO 暫停必須傳回 EL1 主機,以便進一步進行分類。pKVM 需要以下項目:

  • 存取的 IPA 和大小
  • 寫入資料
  • 在擷取點上 CPU 的位元順序

此外,如果陷阱以通用目的註冊器 (GPR) 做為來源/目的地,則會使用抽象轉移偽註冊器轉送。

訪客介面

訪客可以使用超級呼叫和對陷阱區域的記憶體存取權,與受保護的訪客進行通訊。超級呼叫會根據 SMCCC 標準公開,其中保留的範圍由 KVM 供應商分配。以下超級呼叫對 pKVM 客戶端特別重要。

一般超級呼叫

  • PSCI 提供標準機制,讓訪客控管其 vCPU 的生命週期,包括上線、離線和系統關機。
  • TRNG 提供標準機制,讓訪客向 pKVM 要求熵值,並將呼叫轉送至 EL3。如果主機受信任,無法將硬體隨機號碼產生器 (RNG) 虛擬化,這項機制就特別實用。

pKVM 超級呼叫

  • 與主機共用記憶體。最初無法從主機存取所有訪客記憶體,但對於共用記憶體通訊,以及仰賴共用緩衝區的半虛擬化裝置,必須要有主機存取權。與主機共用和取消共用網頁的 Hypercall 可讓訪客決定 Android 的哪些部分可供其他部分存取,而無需握手。
  • 將記憶體釋出給主機。所有訪客記憶體通常都屬於訪客,直到被刪除為止。如果 VM 的長期記憶體需求隨時間改變,這個狀態可能不足。relinquish 超級通話可讓訪客明確將網頁的擁有權轉回主機,而不需要終止訪客。
  • 將記憶體存取轉送至主機。一般來說,如果 KVM 訪客存取的位址與有效記憶體區域不符,則 vCPU 執行緒會退出至主機,而存取權通常會用於 MMIO,並由使用者空間中的 VMM 模擬。為方便處理這個問題,pKVM 必須向主機宣傳錯誤指令的詳細資料,例如地址、註冊參數和可能的內容。如果未預期陷阱,這可能會無意中洩漏受保護的訪客機密資料。pKVM 會將這些錯誤視為致命錯誤,除非訪客機先前已發出超級呼叫,將錯誤的 IPA 範圍視為可將存取權陷回主機的範圍。這項解決方案稱為 MMIO 防護機制

虛擬 I/O 裝置 (virtio)

Virtio 是熱門、可攜式且成熟的標準,適用於實作超虛擬化裝置及進行互動的標準。大部分向受保護的訪客公開的裝置,都是使用 virtio 實作。Virtio 也支援 vsock 實作,用於保護的訪客與 Android 其他部分之間的通訊。

Virtio 裝置通常由 VMM 在主機的使用者空間中實作,該裝置會攔截從訪客到 Virtio 裝置的 MMIO 介面所攔截的記憶體存取,並模擬預期的行為。由於每次存取裝置都需要與 VMM 進行來回傳輸,因此 MMIO 存取成本相對較高,因此裝置和訪客之間的大部分實際資料傳輸作業,都是透過記憶體中的一組 virtqueue 進行。virtio 的一項重要假設是,主機可以任意存取客體記憶體。這項假設在 virtqueue 的設計中顯而易見,其中可能包含指向訪客中緩衝區的指標,而裝置模擬工具會直接存取這些緩衝區。

雖然先前所述的記憶體共用超級呼叫可用於將 virtio 資料緩衝區從訪客共用至主機,但這項共用作業必須以頁面精確度執行,如果緩衝區大小小於頁面大小,可能會導致公開的資料比所需資料還多。相反地,系統會將虛擬佇列和相應的資料緩衝區,從共用記憶體的固定視窗中分配給來賓,並視需要將資料複製 (彈跳) 至視窗和視窗之間。

虛擬裝置

圖 4.Virtio 裝置

與 TrustZone 互動

雖然訪客無法直接與 TrustZone 互動,但主機仍必須能夠向安全世界發出 SMC 呼叫。這些呼叫可指定無法透過主機存取的實體定址記憶體緩衝區。由於安全軟體通常不會知道緩衝區的可存取性,因此惡意主機可能會利用這個緩衝區執行混淆代理攻擊 (類似 DMA 攻擊)。為避免這類攻擊,pKVM 會擷取所有主機 SMC 對 EL2 的呼叫,並在 EL3 中擔任主機和安全監控器之間的 Proxy。

主機的 PSCI 呼叫會傳送至 EL3 韌體,且只需進行最少的修改。具體來說,CPU 上線或從暫停狀態恢復的進入點會重新寫入,以便在返回 EL1 返回主機之前,在 EL2 安裝階段 2 頁面資料表。開機期間,pKVM 會強制執行這項保護機制。

此架構仰賴支援 PSCI 的 SoC,最好是使用最新版本的 TF-A 做為 EL3 韌體。

Arm 韌體架構 (FF-A) 將正常和安全世界之間的互動標準化,尤其是在安全的虛擬機器管理程序中。規格的主要部分會定義與安全世界共用記憶體的機制,同時使用通用訊息格式和基礎頁面明確定義的權限模型。pKVM 會代理 FF-A 訊息,確保主機不會嘗試與沒有足夠權限的安全端共用記憶體。

這個架構依賴強制執行記憶體存取模型的安全世界軟體,確保可信任應用程式和其他在安全環境中執行的任何軟體,只有在其專屬於安全世界擁有,或已透過 FF-A 明確與之共用記憶體的情況下才能存取記憶體。在採用 S-EL2 的系統上,強制記憶體存取模型應由 Secure Partition Manager Core (SPMC) 執行,例如 Hafnium,負責為安全世界維護階段 2 頁表。在沒有 S-EL2 的系統上,TEE 可以改為透過其第 1 階段的頁面表強制執行記憶體存取模型。

如果 EL2 的 SMC 呼叫不是 PSCI 呼叫或 FF-A 定義的訊息,未處理的 SMC 會轉送至 EL3。這項假設是 (必受信任的) 安全韌體可以安全地處理未處理的 SMC,因為韌體瞭解維持 pVM 隔離機制所需的預防措施。

虛擬機器監視器

crosvm 是虛擬機器監視器 (VMM),可透過 Linux 的 KVM 介面執行虛擬機器。crosvm 的獨特之處在於其重心在於使用 Rust 程式設計語言,並在虛擬裝置周圍沙箱保護主機核心。如要進一步瞭解 crosvm,請參閱這份官方說明文件

檔案描述元和 ioctl

KVM 會使用構成 KVM API 的 ioctl,將 /dev/kvm 字元裝置公開給使用者空間。這些 ioctl 屬於下列類別:

  • 系統 ioctl 會查詢並設定影響整個 KVM 子系統的全域屬性,並建立 pVM。
  • 虛擬機器 ioctl 會查詢及設定建立虛擬 CPU (vCPU) 和裝置的屬性,並影響整個 pVM,例如記憶體配置和虛擬 CPU (vCPU) 和裝置的數量。
  • vCPU ioctl 會查詢及設定用於控制單一虛擬 CPU 運作的屬性。
  • 裝置 ioctl 會查詢及設定用於控制單一虛擬裝置運作的屬性。

每個 crosvm 程序都會執行一個虛擬機器執行個體。這項程序會使用 KVM_CREATE_VM 系統 ioctl 建立可用於發出 pVM Octls 的 VM 檔案描述元。VM FD 上的 KVM_CREATE_VCPUKVM_CREATE_DEVICE ioctl 會建立 vCPU/裝置,並傳回指向新資源的檔案描述元。vCPU 或裝置 FD 上的 ioctl 可用來控制在 VM FD 上使用 ioctl 建立的裝置。以 vCPU 來說,這包括執行訪客程式碼的重要工作。

crosvm 會在內部使用邊緣觸發的 epoll 介面,向核心註冊 VM 的檔案描述元。接著,當任何檔案描述元有待處理的新事件時,核心就會通知 crosvm。

pKVM 新增了 KVM_CAP_ARM_PROTECTED_VM 功能,可用於取得 pVM 環境相關資訊,並設定 VM 的受保護模式。如果傳遞 --protected-vm 旗標,crosvm 會在建立 pVM 期間使用這個功能,為 pVM 韌體查詢和保留適當的記憶體量,以及啟用受保護的模式。

記憶體分配

VMM 的主要職責之一,就是分配 VM 的記憶體,並管理記憶體版面配置。crosvm 會產生固定記憶體版面配置,詳情請參閱下表。

在一般模式下執行 FDT PHYS_MEMORY_END - 0x200000
可用空間 ...
Ramdisk ALIGN_UP(KERNEL_END, 0x1000000)
核心 0x80080000
系統啟動載入程式 0x80200000
BIOS 模式的 FDT 0x80000000
實體記憶體基地 0x80000000
pVM 韌體 0x7FE00000
裝置記憶體 0x10000 - 0x40000000

系統會使用 mmap 分配實體記憶體,並將記憶體捐贈給 VM,以便使用 KVM_SET_USER_MEMORY_REGION ioctl 填入其記憶體區域 (稱為 memslots)。因此,所有訪客 pVM 記憶體都會歸因於管理該記憶體的 crosvm 例項,如果主機開始耗盡可用記憶體,可能會導致程序遭到終止 (終止 VM)。當 VM 停止時,虛擬機器會自動由虛擬機器管理程序清除,並傳回至主機核心。

在一般 KVM 中,VMM 會保留對所有訪客記憶體的存取權。使用 pKVM 時,當訪客記憶體捐贈給訪客時,會從主機實體位址空間解除對應。唯一的例外狀況是訪客明確共用的記憶體,例如 virtio 裝置。

在訪客的位址空間中,MMIO 區域未對應。訪客對這些區域的存取權會遭到攔截,並在 VM FD 上產生 I/O 事件。這個機制用於實作虛擬裝置。在保護模式中,輔助程式必須確認其位址空間的區域會使用超級呼叫來進行 MMIO,以降低意外資訊外洩的風險。

時段設定

每個虛擬 CPU 都由 POSIX 執行緒代表,並由主機 Linux 排程器排程。執行緒會在 vCPU FD 上呼叫 KVM_RUN ioctl,導致 Hypervisor 切換至來賓 vCPU 情境。主機排程器會將在訪客內容中花費的時間,視為對應 vCPU 執行緒使用的時間。發生必須由 VMM 處理的事件 (例如 I/O、中斷結束,或 vCPU 已暫停) 時,會傳回 KVM_RUN。VMM 會處理事件,並再次呼叫 KVM_RUN

KVM_RUN 期間,主機排程器仍可搶佔執行緒,但執行 EL2 虛擬器程式碼時則無法搶佔。訪客 PVM 本身並沒有控制這項行為的機制。

由於所有 vCPU 執行緒的排程方式與其他使用者空間工作相同,因此會受到所有標準 QoS 機制的約束。具體來說,每個 vCPU 執行緒都可以與實體 CPU 相依,放置在 CPU 集合中,並使用利用率固定值進行提升或限制,還可以變更其優先順序/排程政策等等。

虛擬裝置

crosvm 支援多種裝置,包括:

  • 用於組合磁碟映像檔的 virtio-blk,可設定為唯讀或讀寫
  • vhost-vsock,用於與主機通訊
  • 使用 virtio-pci 做為 virtio 傳輸
  • pl030 即時時鐘 (RTC)
  • 16550a UART 用於序列通訊

pVM 韌體

pVM 韌體 (pvmfw) 是 pVM 執行的第一個程式碼,類似實體裝置的啟動 ROM。pvmfw 的主要目標是啟動安全啟動程序,並擷取 pVM 的專屬機密。只要 crosvm 支援該作業系統且已正確簽署,pvmfw 即可用於任何特定作業系統,例如 Microdroid

pvmfw 二進位檔會儲存在同名快閃分割區,並使用 OTA 進行更新。

裝置啟動

下列步驟會加入支援 pKVM 的裝置啟動程序:

  1. Android 啟動載入器 (ABL) 會從其分區將 pvmfw 載入記憶體,並驗證映像檔。
  2. ABL 會從信任根 (ABL) 取得裝置識別碼組合引擎 (DICE) 密鑰 (複合裝置識別碼 (CDI) 和 DICE 憑證鏈結)。
  3. ABL 會為 pvmfw 衍生必要的 CDIs,並將其附加至 pvmfw 二進位檔。
  4. ABL 會將 linux,pkvm-guest-firmware-memory 保留的記憶體區域節點新增至 DT,說明 pvmfw 二進位檔的位置和大小,以及先前步驟中衍生的機密資料。
  5. ABL 將控制權交給 Linux,而 Linux 會初始化 pKVM。
  6. pKVM 會從主機的階段 2 頁面表中取消對應 pvmfw 記憶體區域,並在裝置的整個正常運作期間,保護 pvmfw 免受主機 (和訪客) 的影響。

裝置啟動後,Microdroid 會按照 Microdroid 文件中「啟動順序」一節的步驟啟動。

pVM 啟動

建立 pVM 時,crosvm (或其他 VMM) 必須建立足夠大的 memslot,以便由輔助作業系統填入 pvmfw 映像檔。VMM 也只限於可以設定初始值的暫存器清單中 (主要 vCPU 為 x0-x14,次要 vCPU 無)。其餘的暫存器會保留,並屬於 hypervisor-pvmfw ABI。

pVM 執行時,管理程序會將主要 vCPU 從 pvmfw 的首手控制權交給 pvmfw。韌體預期 crosvm 已載入 AVB 簽署的核心 (可為系統啟動載入程式或任何其他映像檔),以及未簽署的 FDT 至已知偏移量記憶體。pvmfw 會驗證 AVB 簽名,如果成功,則會從收到的 FDT 產生信任的裝置樹狀結構、從記憶體中清除其機密資料,並分支至酬載的進入點。如果其中一個驗證步驟失敗,韌體會發出 PSCI SYSTEM_RESET 超級呼叫。

在兩次開機之間,pVM 執行個體的相關資訊會儲存在分割區 (virtio-blk 裝置) 中,並使用 pvmfw 的密鑰進行加密,確保在重新啟動後,密鑰會佈建至正確的執行個體。