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 啟動,然後由虛擬機器管理程序將這些頁面的擁有權從主機轉移至 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 中的所有現有基礎架構。最後,等待中斷 (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 的其他部分存取,而無需握手。
  • 將記憶體釋出給主機。所有訪客記憶體通常都屬於訪客,直到遭到刪除為止。對於記憶體需求隨時間變化的長效虛擬機,這項狀態可能不夠完善。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 的系統中,安全分割區管理員核心 (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 ioctl 的 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 執行緒使用的時間。KVM_RUN 會在必須由 VMM 處理的事件 (例如 I/O、中斷結束或 vCPU 停止) 時傳回。VMM 會處理事件,並再次呼叫 KVM_RUN

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

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

虛擬裝置

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 會從信任根取得裝置 ID 組合引擎 (DICE) 的機密資料 (複合裝置 ID (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 記憶體區域,並在裝置的整個正常運作期間,保護該區域免受主機 (和訪客) 的影響。

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

pVM 啟動

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

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

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