Android 提供了實作 Android 虛擬化架構所需的所有元件的參考實作。目前這項實作僅適用於 ARM64。本頁面說明架構架構,
背景
Arm 架構允許最多四個例外狀況層級,例外狀況層級 0 (EL0) 具備最低權限,例外狀況層級 3 (EL3) 則最多。Android 程式碼集上最大的部分 (所有使用者空間元件) 會在 EL0 執行。其餘常見的「Android」則是在 EL1 執行的 Linux 核心。
EL2 層可讓您導入管理程序,將記憶體和裝置隔離在 EL1/EL0 的個別 pVM,同時具有高度的機密性和完整性保證。
管理程序
受保護的核心虛擬機器 (pKVM) 是以 Linux KVM 管理程序為基礎,這個架構已延伸,可限制在建立時標示為「受保護」的訪客虛擬機器中執行的酬載。
KVM/arm64 會根據特定 CPU 功能的可用性,支援不同的執行模式,也就是虛擬化主機擴充功能 (ARMv8.1 以上版本)。在這些模式中 (通常稱為非 VHE 模式) 中,管理程序程式碼會在啟動期間從核心映像檔中分離出來並安裝在 EL2,而核心本身是在 EL1 執行。雖然 Linux 程式碼集的一部分,KVM 的 EL2 元件仍是負責切換多個 EL1 的小型元件。管理程序元件是透過 Linux 編譯,但位於 vmlinux
映像檔的個別專屬記憶體區段中。利用這項設計,pKVM 會利用新功能擴充管理程序程式碼,對 Android 主機核心和使用者空間設下限制,並限制主機對訪客記憶體和管理程序的存取權。
pKVM 供應商模組
pKVM 供應商模組是硬體專屬模組,內含裝置專屬功能,例如輸入輸出記憶體管理單位 (IOMMU) 驅動程式。這些模組可讓您移植安全性功能,以便存取 pKVM 需要例外狀況層級 2 (EL2) 的權限。
如要瞭解如何實作及載入 pKVM 供應商模組,請參閱「實作 pKVM 供應商模組」。
啟動程序
下圖說明 pKVM 啟動程序:
- 系統啟動載入程式進入 EL2 的一般核心。
- 一般核心會偵測其是在 EL2 執行,並將自身降級為 EL1,而 pKVM 及其模組會繼續在 EL2 執行。此外,系統目前會載入 pKVM 供應商模組。
- 一般核心會繼續正常啟動,載入所有必要的裝置驅動程式,直到到達使用者空間為止。此時,pKVM 已就緒,並負責處理 Stage-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。
過去 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 所示。
至少需提供 IOMMU 硬體,才能在頁面精細程度上授予及撤銷裝置對實體記憶體的讀取/寫入權限。不過,這個 IOMMU 硬體會限制 pVM 中的裝置使用,因為這些裝置會假設為識別資訊對應階段 2。
為確保能在不同虛擬機器之間隔離,IOMMU 必須能區別代表不同實體產生的記憶體交易,這樣才能使用適當的頁面資料表組合轉譯翻譯。
此外,在 EL2 中減少 SoC 特定程式碼的數量是減少 pKVM 整體受信任運算基礎 (TCB) 的關鍵策略,並顧及在管理程序中加入 IOMMU 驅動程式的計數器。為緩解這個問題,EL1 的主機負責執行輔助 IOMMU 管理工作,例如電源管理、初始化,以及在適當情況下中斷處理。
不過,將主機控制裝置狀態,對 IOMMU 硬體的程式設計介面設有額外要求,確保無法透過其他方式規避權限檢查 (例如在裝置重設後)。
Arm 系統記憶體管理單元 (SMMU) 架構是針對 Arm 裝置提供個別支援且完整支援的 IOMMU,以便進行隔離和直接指派。我們推薦您使用這個架構做為參考解決方案。
記憶體擁有權
啟動時,系統會假設所有非管理程序記憶體都由主機擁有,並由管理程序加以追蹤。產生 pVM 時,主機會提供記憶體頁面以允許其啟動,且管理程序會將這些頁面的擁有權從主機轉移至 pVM。因此,管理程序會在主機的階段 2 頁面表格中設定存取權控管限制,避免該頁面再次存取頁面,向訪客提供機密性。
主機和訪客之間的通訊是藉由受控管的記憶體共用而完成。訪客可使用 Hyper 呼叫,將部分頁面傳回主機,指示管理程序將主機階段 2 頁表格中的這些頁面重新對應。同樣地,主機與 TrustZone 的通訊是由記憶體共用和/或借貸作業促成,而這些作業全都由 pKVM 使用 Arm 韌體架構 (FF-A) 規格密切監控和控管。
由於 pVM 的記憶體需求可能會隨時間變化,因此系統會提供超呼叫,允許屬於呼叫端的指定頁面重新排回主機。實際上,這個超呼叫會搭配 virtio 說明框通訊協定使用,以允許 VMM 從 pVM 要求記憶體,而 pVM 會以受控的方式通知相關頁面的 VMM。
管理程序負責追蹤系統中所有記憶體頁面的擁有權,以及這些頁面是否與其他實體共用或出借。大部分的狀態追蹤是使用附加至主機和訪客階段 2 頁面表格的中繼資料,使用頁面表格項目 (PTE) 中的保留位元 (如名稱所示),作為軟體使用而保留供軟體使用。
主機必須確認該主機不會嘗試存取管理程序已禁止存取的頁面。非法的主機存取會導致管理程序將同步例外狀況插入主機,進而導致負責任的使用者空間工作接收 SEGV 信號,或是主機核心停止運作。為避免意外存取,提供給訪客的頁面會遭到主機核心交換或合併。
中斷處理和計時器
中斷是訪客與裝置互動以及 CPU 之間通訊的重要一環,因為處理者中斷 (IPI) 是主要的通訊機制。KVM 模式會將所有虛擬中斷管理委派給 EL1 中的主機,這個目的的行為就像管理程序的不受信任。
pKVM 可根據現有 KVM 程式碼提供完整的通用中斷控制器第 3 版 (GICv3) 模擬功能。在這個不受信任的模擬程式碼中,系統會處理計時器和 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 超呼叫
- 與主機共用記憶體。最初無法從主機存取所有訪客記憶體,但對於共用記憶體通訊,以及仰賴共用緩衝區的半虛擬化裝置,必須要有主機存取權。針對分享和取消與主機分享網頁的超呼叫,訪客可以決定哪些記憶體部分可供 Android 其他部分存取,而不必握手。
- 預留給主機的記憶體。所有訪客記憶體通常都屬於訪客,直到被刪除為止。如果 VM 的長期記憶體需求隨時間改變,這個狀態可能不足。
relinquish
超呼叫可讓訪客將頁面的擁有權明確轉移回主機,而不必終止訪客。 - 將記憶體存取轉送至主機。一般來說,如果 KVM 訪客存取的位址未對應至有效記憶體地區,vCPU 執行緒會退出主機,而存取權通常用於 MMIO,並由 VMM 在使用者空間中模擬。為加速處理,pKVM 必須通告錯誤指示的細節 (例如位址)、註冊參數,且可能將其內容傳回主機。如果被預期偵測不到,則可能會意外向受保護訪客公開機密資料。除非訪客之前核發的 IP 錯誤,以處理這些錯誤,以識別由主機核發的 IP 錯誤,以處理這些錯誤,這個解決方案稱為「MMIO 保護」。
虛擬 I/O 裝置 (virtio)
Virtio 是熱門、可攜式且成熟的標準,適用於實作半虛擬化裝置及與其互動。大多數暴露在受保護訪客的裝置上,都是使用 virtio 實作。Virtio 還奠定了對受保護訪客與 Android 其餘部分之間通訊的 vsock 實作的基礎。
Virtio 裝置通常會由 VMM 在主機的使用者空間中實作,VMM 會攔截已竊取的記憶體存取,從訪客存取 virtio 裝置的 MMIO 介面,並模擬預期的行為。MMIO 存取成本相對昂貴,因為每次存取裝置都需要往返 VMM 和返回位置,因此大部分在裝置和訪客之間實際的資料傳輸都是使用一組記憶體中的 Virt 佇列。Virtio 的一項重要假設是,主機可以任意存取訪客記憶體。這個假設在 virtQueue 設計中明顯,可能包含了裝置模擬作業是直接存取,而訪客中緩衝區的指標。
雖然上述的記憶體共用超呼叫可用於將訪客與主機共用 Virtio 資料緩衝區,但這項共用作業必須在頁面精細程度下執行,而且如果緩衝區大小小於頁面頁面,可能會揭露多餘的資料。相反地,訪客設定為從固定的共用記憶體視窗分配 virtQueue 及其對應的資料緩衝區,並在需要時將資料複製 (彈跳) 到視窗之間,再從該視窗複製資料。
與 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 Proxy FF-A 訊息,以確保主機不會嘗試與安全端共用記憶體,因為其不具備足夠的權限。
這個架構依賴強制執行記憶體存取模型的安全世界軟體,確保可信任應用程式和其他在安全環境中執行的任何軟體,只有在其專屬於安全世界擁有,或已透過 FF-A 明確與之共用記憶體的情況下才能存取記憶體。在採用 S-EL2 的系統上,強制記憶體存取模型應由 Secure Partition Manager Core (SPMC) 執行,例如 Hafnium,負責為安全世界維護階段 2 頁表。在沒有 S-EL2 的系統上,TEE 可以改為透過第 1 頁表格強制執行記憶體存取模型。
如果 SMC 對 EL2 的呼叫不是 PSCI 呼叫或 FF-A 定義訊息,則會將未處理的 SMC 轉送到 EL3。這項假設是 (必受信任的) 安全韌體可以安全地處理未處理的 SMC,因為韌體瞭解維持 pVM 隔離措施所需的預防措施。
虛擬機器監控器
crosvm 是一種虛擬機器監視器 (VMM),可透過 Linux 的 KVM 介面執行虛擬機器。crosvm 的獨特之處在於其重心在於使用 Rust 程式設計語言,並在虛擬裝置周圍沙箱保護主機核心。如要進一步瞭解 crosvm,請參閱這裡的官方說明文件。
檔案描述元和 ioctls
KVM 會使用構成 KVM API 的 ioctls 向使用者空間公開 /dev/kvm
字元裝置。橢圓形屬於下列類別:
- 系統 ioctls 查詢並設定會影響整個 KVM 子系統的全域屬性,然後建立 pVM。
- VM IOctls 查詢並設定屬性來建立虛擬 CPU (vCPU) 和裝置,並影響整個 pVM,例如記憶體配置和虛擬 CPU (vCPU) 和裝置數量。
- vCPU 查詢和設定屬性,藉此控管單一虛擬 CPU 的作業。
- 裝置 ioctls 查詢和設定屬性,用於控制單一虛擬裝置的運作。
每個 crosvm 程序只會執行一個虛擬機器的執行個體。這項程序會使用 KVM_CREATE_VM
系統 ioctl 建立可用於發出 pVM Octls 的 VM 檔案描述元。VM FD 上的 KVM_CREATE_VCPU
或 KVM_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,進而將管理程序切換至訪客 vCPU 結構定義。主機排程器會考量訪客背景資訊,做為對應 vCPU 執行緒使用的時間。發生必須由 VMM 處理的事件 (例如 I/O、中斷結束,或 vCPU 已暫停) 時,會傳回 KVM_RUN
。VMM 會處理該事件並再次呼叫 KVM_RUN
。
在 KVM_RUN
期間,執行緒仍可由主機排程器先佔,但執行非先佔的 EL2 管理程序程式碼除外。訪客 PVM 本身並沒有控制這項行為的機制。
由於所有 vCPU 執行緒的排程方式與其他使用者空間工作相同,因此必須遵循所有標準 QoS 機制。具體來說,每個 vCPU 執行緒都可以用於實體 CPU、放置在 cpuses 中、以使用率取值調整強化或設有上限,以及變更優先順序/排程政策等。
虛擬裝置
crosvm 支援多種裝置,包括:
- virtio-blk,適用於複合磁碟映像檔,唯讀或讀寫
- 與主持人通訊的 vhost-vsock
- Virtio-pci 作為 virtio 運輸
- pl030 即時時鐘 (RTC)
- 16550a UART (適用於序列通訊)
pVM 韌體
pVM 韌體 (pvmfw) 是由 pVM 執行的第一個程式碼,類似實體裝置的啟動 ROM。pvmfw 的主要目標是啟動安全啟動,並導出 pVM 的專屬密鑰。只要符合任何特定 OS (例如 Microby OS 即正確簽署且已妥善支援pvmfw) 即可。
pvmfw 二進位檔儲存在同名的 Flash 分區中,並使用 OTA 更新。
裝置啟動
下列步驟順序會加入支援 pKVM 裝置的啟動程序:
- Android 系統啟動載入程式 (ABL) 將其分區的 pvmfw 載入記憶體並驗證映像檔。
- ABL 會從信任根 (ABL) 取得裝置識別碼組合引擎 (DICE) 密鑰 (複合裝置識別碼 (CDI) 和 DICE 憑證鏈結)。
- ABL 會產生 pvmfw 所需的 CDI,並將其附加到 pvmfw 二進位檔中。
- ABL 會將
linux,pkvm-guest-firmware-memory
保留的記憶體區域節點新增至 DT,說明 pvmfw 二進位檔的位置和大小,以及該二進位檔在上一步中衍生的密鑰。 - Linux 和 Linux 的 ABL 握手控制會初始化 pKVM。
- pKVM 會從主機的第 2 階段表格取消對應 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 的密鑰加密,以確保在重新啟動後,系統會將密鑰佈建至正確的執行個體。