AVF 架構

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

背景

Arm 架構最多允許四個例外狀況層級,其中例外狀況層級 0 (EL0) 的權限最低,例外狀況層級 3 (EL3) 的權限最高。Android 程式碼集的最大部分 (所有使用者空間元件) 都在 EL0 執行。一般所謂的「Android」其餘部分是 Linux 核心,在 EL1 執行。

EL2 層可導入管理程序,在 EL1/EL0 將記憶體和裝置隔離到個別 pVM 中,並提供嚴格的機密性和完整性保證。

管理程序

受保護的 KVM (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 Hypervisor 部署到 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 硬體會限制在 pVM 中使用裝置,因為這些裝置會假設身分對應的第 2 階段。

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

此外,減少 EL2 的 SoC 專屬程式碼量,是降低 pKVM 整體信任運算基礎 (TCB) 的重要策略,與在 Hypervisor 中納入 IOMMU 驅動程式的做法背道而馳。為解決這個問題,EL1 的主機負責輔助 IOMMU 管理工作,例如電源管理、初始化,以及適當的中斷處理。

不過,將裝置狀態的控制權交給主機,會對 IOMMU 硬體的程式設計介面提出額外要求,確保權限檢查不會遭到規避,例如在裝置重設後。

Arm 裝置的標準 IOMMU 支援良好,可實現隔離和直接指派功能,也就是 Arm 系統記憶體管理單元 (SMMU) 架構。建議您採用這項架構做為參考解決方案。

記憶體擁有權

在啟動時,所有非管理程序記憶體都會假設由主機擁有,並由管理程序追蹤。產生 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 主機可看到完整的中斷狀態,包括與中斷相關的 Hypervisor 暫存器副本。這項可見度通常是透過共用記憶體區域達成,每個虛擬 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 guest 而言特別重要。

一般超呼叫

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

pKVM 管理程序呼叫

  • 與主辦人分享記憶體。主機一開始無法存取所有訪客記憶體,但共用記憶體通訊和依賴共用緩衝區的半虛擬化裝置都需要主機存取權。透過與主機共用及取消共用頁面的超呼叫,訪客可決定要讓 Android 的其餘部分存取哪些記憶體,不必進行交握。
  • 將記憶體釋放給主機。所有訪客記憶體通常都屬於訪客,直到記憶體遭到刪除為止。對於記憶體需求會隨時間變化的長期 VM 而言,這種狀態可能不夠充分。訪客可透過 relinquish 超級呼叫,將頁面擁有權明確轉移回主機,不必終止訪客。
  • 記憶體存取會陷阱到主機。傳統上,如果 KVM 訪客存取的位址不對應至有效記憶體區域,vCPU 執行緒就會結束並返回主機,而存取權通常用於 MMIO,並由使用者空間中的 VMM 模擬。為方便處理這類情況,pKVM 必須向主機宣傳有關錯誤指令的詳細資料,例如位址、暫存器參數和可能內容,如果未預期發生陷阱,可能會無意間從受保護的訪客公開機密資料。pKVM 會將這些錯誤視為嚴重錯誤,除非訪客先前已發出超呼叫,將錯誤 IPA 範圍識別為允許存取以陷阱形式返回主機的範圍,否則不會處理。這項解決方案稱為「MMIO 防護」

虛擬 I/O 裝置 (virtio)

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

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

雖然可以使用上述記憶體共用超呼叫,將 virtio 資料緩衝區從訪客共用至主機,但這種共用作業必須以頁面精細度執行,如果緩衝區大小小於頁面大小,最終可能會公開超出需求的資料。而是設定為從固定大小的共用記憶體配置虛擬佇列和對應的資料緩衝區,並視需要將資料複製 (彈跳) 到該視窗或從該視窗複製資料。

虛擬裝置

圖 4. Virtio 裝置

與 TrustZone 互動

雖然邀請對象無法直接與 TrustZone 互動,但主辦人仍須能夠在安全世界中發出 SMC 呼叫。這些呼叫可以指定主機無法存取的實體定址記憶體緩衝區。由於安全軟體通常不會察覺緩衝區的可存取性,惡意主機可能會使用這個緩衝區執行混淆代理攻擊 (類似於 DMA 攻擊)。為防止這類攻擊,pKVM 會將所有主機 SMC 呼叫陷阱設為 EL2,並做為主機與 EL3 層級安全監控程式之間的 Proxy。

主機的 PSCI 呼叫會轉送至 EL3 韌體,且修改次數最少。具體來說,CPU 上線或從暫停狀態恢復時的進入點會經過重寫,以便在返回 EL1 的主機前,於 EL2 安裝第 2 階段的頁面表格。在開機期間,pKVM 會強制執行這項保護措施。

這項架構依賴 SoC 支援 PSCI,最好是使用最新版 TF-A 做為 EL3 韌體。

Arm 韌體架構 (FF-A) 會將一般和安全世界之間的互動標準化,特別是在有安全 Hypervisor 的情況下。規格的主要部分定義了與安全世界共用記憶體的機制,包括使用通用訊息格式,以及為基礎頁面定義完善的權限模型。pKVM 會將 FF-A 訊息設為 Proxy,確保主機不會嘗試與安全端共用記憶體,但主機沒有足夠的權限。

這項架構依賴安全世界軟體強制執行記憶體存取模型,確保在安全世界中執行的受信任應用程式和任何其他軟體,只能存取安全世界專屬的記憶體,或是使用 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 建立 VM 檔案描述元,可用於發出 pVM ioctl。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 填入記憶體區域 (稱為 memslot)。因此,所有客體 pVM 記憶體都會歸因於管理該記憶體的 crosvm 執行個體,如果主機開始耗盡可用記憶體,可能會導致程序遭到終止 (終止 VM)。VM 停止時,管理程序會自動清除記憶體,並將記憶體傳回主機核心。

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

訪客位址空間中的 MMIO 區域會保持未對應狀態。訪客對這些區域的存取要求會遭到攔截,並在 VM FD 上產生 I/O 事件。這項機制用於實作虛擬裝置。在受保護模式下,訪客必須使用超呼叫確認其位址空間的區域用於 MMIO,以降低資訊意外洩漏的風險。

時段設定

每個虛擬 CPU 都以 POSIX 執行緒表示,並由主機 Linux 排程器排程。執行緒會在 vCPU FD 上呼叫 KVM_RUN ioctl,導致管理程序切換至客體 vCPU 環境。主機排程器會將在訪客環境中耗費的時間,計為對應 vCPU 執行緒所用的時間。KVM_RUN 會在發生必須由 VMM 處理的事件時傳回,例如 I/O、中斷結束或 vCPU 停止。VMM 會處理事件,並再次呼叫 KVM_RUN

KVM_RUN 期間,執行緒仍可由主機排程器搶占,但 EL2 Hypervisor 程式碼的執行作業除外,因為這類作業無法搶占。客體 pVM 本身沒有任何機制可控制這項行為。

由於所有 vCPU 執行緒的排程方式與其他使用者空間工作相同,因此會受到所有標準 QoS 機制的影響。具體來說,每個 vCPU 執行緒都可以與實體 CPU 建立親和性、放置在 CPU 集、透過利用率鉗制功能提升或限制,以及變更優先順序/排程政策等。

虛擬裝置

crosvm 支援多種裝置,包括:

  • 複合磁碟映像檔的 virtio-blk,唯讀或讀寫
  • 用於與主機通訊的 vhost-vsock
  • 以 virtio 傳輸方式運作的 virtio-pci
  • pl030 即時時鐘 (RTC)
  • 用於序列通訊的 16550a UART

pVM 韌體

pVM 韌體 (pvmfw) 是 pVM 執行的第一個程式碼,類似於實體裝置的開機 ROM。pvmfw 的主要目標是啟動安全啟動程序,並衍生 pVM 的專屬密碼。只要 OS 受到 crosvm 支援且已正確簽署,pvmfw 就不會限制與任何特定 OS (例如 Microdroid) 搭配使用。

pvmfw 二進位檔會儲存在同名的快閃分割區中,並透過 OTA 更新。

裝置啟動

下列步驟順序會新增至啟用 pKVM 的裝置啟動程序:

  1. Android Bootloader (ABL) 會將 pvmfw 從其分割區載入記憶體,並驗證映像檔。
  2. ABL 會從信任根取得裝置 ID 合成引擎 (DICE) 密鑰 (複合裝置 ID (CDI) 和 DICE 憑證鏈結)。
  3. ABL 會衍生 pvmfw 的必要 CDI,並將其附加至 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 也受到限制,只能在可設定初始值的暫存器清單中設定值 (主要 vCPU 為 x0-x14,次要 vCPU 則無)。其餘暫存器會保留,並屬於管理程序監控程式-pvmfw ABI。

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

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