RFC-0261:快速且有效率的使用者空間核心模擬 | |
---|---|
狀態 | 已接受 |
區域 |
|
說明 | 讓 Fuchsia 程式擔任不受信任的客體程式安全又有效率的監督者。 |
問題 | |
Gerrit 變更 | |
作者 | |
審查人員 | |
提交日期 (年-月-日) | 2022-06-07 |
審查日期 (年-月-日) | 2024-10-01 |
摘要
本文件提出一組功能,讓 Fuchsia 程式可做為不受信任的來賓程式碼安全且有效率的監控器,而無須使用虛擬機器人技術。主要用途是讓 Starnix 執行元件更快、更有效率。
這些功能有時統稱為「限制模式」。在 Starnix 的情況下,這些功能是 Starnix 執行模型的基礎。
本文件著重於 Zircon 核心,並介紹新的核心功能。具體來說,Starnix 如何運用這些功能,不在本文討論範圍。
提振精神
如需背景資訊,請參閱 RFC-0082:在 Fuchsia 上執行未修改的 Linux 程式。
我們希望開發 Fuchsia 程式時,能有效模擬其他作業系統的介面。特別是,我們希望在 Fuchsia 上執行未經修改的 Linux 程式,並達到接近原生效能。
相關人員
協助人員:davemoore@google.com
審查者:abarth@google.com、adanis@google.com、cpu@google.com、jamesr@google.com、lindkvist@google.com、mvanotti@google.com
諮詢對象:abarth@google.com、adanis@google.com、rashaeqbal@google.come、lindkvist@google.com、mcgrathr@google.com、mvanotti@google.com
社交化:我們已透過電子郵件、即時通訊和多個核心演進工作小組 (Kernel Evolution Working Group, KEWG) 工作坊,與核心和 Starnix 團隊進行這項提案的社交化。
目標
以下目標可作為設計此系統的參考依據。
快速且有效率的模擬功能:這個新機制必須提供比 Zircon 例外狀況機制更快速且有效率的模擬功能。
強大的安全防護界線:此機制必須充當強大的安全防護界線,並允許安全執行不受信任的程式碼。
自然且簡化的程式設計模型:這個機制提供的程式設計模型不應只用於模擬 Linux 核心介面,還應處理大部分繁重的工作。就自然而言,大多數核心會將核心程式碼和資料放在一個共用位址空間中,因此如果這個新機制提供共用位址空間的程式設計模式,就會很方便 (詳情請參閱「共用狀態」一節)。就簡單來說,我們偏好在較高層級的 OS 抽象層級上建立 OS,而非在較低層級的硬體抽象層級上建立 OS。
分隔複雜性:我們希望將 Zircon 核心的變更分隔開來,以便更妥善地管理複雜性並保持彈性。具體來說,這表示盡可能減少這個機制在核心子系統中切割的程度。
範圍
這項提案的範圍包括模型和一般方法 (詳見「設計」一節),以及系統呼叫介面 (詳見「實作」一節)。
設計
這個系統由三個設計元素組成,可搭配使用。
這項設計的主要元素是限制模式、直接存取和共用狀態。兩者結合後,可提供多程序、共用記憶體的程式設計模式,在 Zircon 抽象層上實作使用者空間核心。
在這個架構中,每個訪客執行緒都會由一個 Zircon 執行緒模擬,每個訪客程序都會由一個 Zircon 程序模擬。每個 Zircon 程序都會有一個位址空間區域,所有程序都會共用這個區域。對訪客核心開發人員而言,共用區域會讓訪客核心看起來和感覺上更像單一多執行緒程式。
元素 1:嚴格篩選模式
這是核心元素。
目前,執行緒在執行時會在使用者模式或核心模式中執行。當執行緒發出系統呼叫時,系統會透過模式切換,從使用者模式轉換至核心模式。完成系統呼叫後,系統會轉回使用者模式。
注意:為簡單起見,我們會在本文件中使用 ARMv8a 術語,除非另有指定。
當然,系統呼叫並非唯一會導致模式切換的事件。核心可能需要處理的任何事件都會觸發模式切換至核心模式。包括頁面錯誤、例外狀況和中斷。
我們推出了第三種選用模式,稱為「嚴格篩選模式」。這項新模式為選用模式,因為系統中並非所有執行緒都會進入受限模式。就像使用者模式相較於核心模式有較多執行環境限制一樣,限制模式也比使用者模式有更多限制。
雖然核心模式和使用者模式會對應至硬體功能 (例如 ARMv8-A 中的 EL1 和 EL0),但受限模式不會。這是一個純軟體結構體。硬體不瞭解嚴格篩選模式。從硬體的角度來看,這只是使用者模式。
術語:為了避免混淆,當我們討論包含所有三種模式的執行緒時,會將未受限制的使用者模式稱為「一般模式」。
概念上,嚴格篩選模式位於一般模式下方。在核心模式下執行的程式碼會充當使用者模式程式的監視器,在一般模式下執行的程式碼則會充當受限模式程式的訪客核心或使用者模式監視器。
隔離
嚴格篩選模式的一大特色是隔離功能。在切換一般模式和受限模式時,變更可存取的位址空間範圍和註冊狀態,即可達到隔離效果。
通常,程序的位址空間會分成兩部分,其中一個部分 (核心位址空間) 只能在執行緒處於核心模式時存取,另一個部分 (使用者位址空間) 則可隨時存取。在限制模式下,使用者位址空間會進一步劃分,提供總共三個區域。
只有在執行緒處於核心模式時,才能存取最上層的區域。中間區域只能透過核心模式或一般模式存取。而下方區域則隨時可供存取 (詳情請參閱「直接存取」一節)。這個較低的區域稱為受限制區域,因為這是受限制模式中執行緒可存取的唯一區域。
進入限制模式後,Zircon 核心會以一般模式提供的值載入通用目的註冊。清除或載入一般用途的註冊器,是一般模式程式碼的責任。舉例來說,Zircon 核心不會清除或載入浮點或向量暫存器。x86 的 fs.base
和 gs.base
、arm64 的 tpidr_el0
,以及 riscv64 的 tp
都視為通用暫存器。詳情請參閱「輸入」一節。
執行緒進入受限模式後,只能存取其註冊狀態和受限區域中的對應項目。
在受限模式下,Zircon 核心會將所有系統呼叫路由至一般模式,無論這些系統呼叫是否由 Zircon vDSO 發出 (下一節會進一步說明這一點)。也就是說,即使 vDSO 已對應至受限制模式,系統仍會將系統呼叫重新導向至一般模式,而非由 Zircon 核心處理。
模式切換和攔截
本節說明執行緒如何在正常模式和受限模式之間轉換。
在轉換時傳達模式狀態
為了提升效能,Zircon 核心和訪客核心會使用共用記憶體 (模式狀態 VMO),在執行緒在一般模式和受限模式之間轉換時,傳遞執行緒的註冊狀態和其他詳細資料。詳情請參閱「準備」一節。
進入嚴格篩選模式
Zircon 核心會使用核心執行緒結構上的標記,追蹤執行緒處於受限模式或一般模式。只要知道執行緒處於受限或一般模式,核心就能正確地將系統呼叫、錯誤和例外狀況「路由」至正確的位置 (核心或一般模式)。
系統會使用新的專屬系統呼叫 (zx_thread_enter_restricted) 從一般模式進入受限制模式。在呼叫之前,使用者模式的監督器會先填入結構體,其中包含要在受限制模式中使用的註冊值。這個結構體位於模式狀態 VMO 中。
進入作業包括將執行緒的可存取位址空間從程序的完整位址空間變更為程序的受限區域 (使用者位址空間的下半部)、從模式狀態 VMO 載入註冊狀態,以及簡單地恢復執行 (請想想將分支至已還原的 PC)。
為了提升效能,Zircon 核心不會自動儲存執行緒的一般模式登錄狀態。呼叫端可以儲存自己在意的狀態。此外,除了通用 (整數) 註冊外,Zircon 核心不會嘗試還原任何受限制模式註冊狀態。呼叫端可自行決定是否要還原更多內容。如要瞭解通用目的登錄包含的內容,請參閱「輸入」一節中的 zx_restricted_state_t
。
離開嚴格篩選模式
在討論執行緒如何離開限制模式並返回一般模式之前,我們先簡單說明執行緒事件在限制模式執行期間發生時,如何處理這三種事件:
中斷和可解決的錯誤 - 中斷和可解決的錯誤不會導致執行緒離開受限模式。硬體和軟體中斷,以及在執行緒處於限制模式時發生的可解決頁面錯誤,基本上對執行緒 (包括其使用者模式監控器) 而言是不可見的。舉例來說,假設 CPU 的平台計時器觸發,且 CPU 進入核心模式。或裝置中斷事件觸發。或者,CPU 會取得由 Zircon 核心解決的頁面錯誤。執行緒 (一般模式程式碼或受限模式程式碼) 不會觀察到任何這些事件。受限模式中的執行緒可完全搶先,與在一般模式下執行時相同。
工作暫停:工作暫停作業不會導致執行緒離開受限模式。嚴格模式不會提供任何「保護」功能,以防發生暫停作業 (例如 zx_task_suspend
)。如果暫停信號傳送時,執行緒恰好處於嚴格模式,則會在嚴格模式下暫停,並在恢復時繼續執行,就好像沒有發生任何事一樣。
偵錯工具例外狀況:如果已附加偵錯工具,且執行中的執行緒處於受限制模式並產生例外狀況 (例如命中中斷點),Zircon 會照常產生偵錯例外狀況,但不會強制執行緒退出受限制模式。不過,如果未附加偵錯工具,例外狀況會導致執行緒離開受限制模式,讓使用者模式監控器處理。另請參閱「例外狀況離開」。
在瞭解這個背景資訊後,我們來討論執行緒如何離開受限模式並返回一般模式。有兩種方式,透過系統呼叫離開和透過例外狀況離開。對於所有人來說,Zircon 核心會提供受限一般用途登錄狀態的副本,以及指出執行緒傳回原因的理由代碼。視離開限制模式的原因而定,Zircon 核心可能會提供其他資訊。在執行緒返回一般模式之前,系統會將此狀態寫入模式狀態 VMO。
透過系統呼叫離開
在受限制模式下執行系統呼叫指令會強制執行緒返回一般模式,以便系統呼叫可由使用者模式監視器處理。
執行系統呼叫指令時,CPU 會先陷入核心模式。核心中的一般系統呼叫路徑會測試執行緒是否處於受限模式。如果是這樣,系統會採用替代路徑,包括儲存受限制的通用目的登錄器狀態,並直接返回正常模式,以便傳回原始系統呼叫中傳入的地址。接著,一般模式必須復原其已儲存的註冊狀態,然後處理系統呼叫。從邏輯上來說,控制權會從受限模式傳遞至一般模式,但實際上會在每個階段透過核心模式彈跳。範例如下:
- 受限模式會發出系統呼叫
- 系統呼叫來自受限制模式,因此核心會返回一般模式,以便處理呼叫
- 一般模式會處理呼叫,並透過系統呼叫 (邏輯上) 傳回至受限制模式
- 核心處理程序處理「進入受限模式的系統呼叫」並跳回受限模式
如先前所述,Zircon 核心執行的註冊狀態儲存和還原作業會盡量減少,以便讓一般模式只儲存/還原所需的內容,進而提升效能。從限制模式切換至/切換至限制模式的費用下限取決於架構。舉例來說,在 x86 上,我們需要交換頁面表根目錄 (CR3
),而在 arm64 上,我們可以使用 TCR_EL1.T0SZ
在位址空間的正常一半執行較便宜的「遮罩/解除遮罩」作業,藉此保留更多正常和受限制模式共用的 TLB 項目。
例外狀況結束
如先前所述,在受限制模式下執行時,Zircon 核心會以透明方式處理可解決的頁面錯誤。也就是說,雖然執行緒可能會實際進入核心模式來解決錯誤,但不會返回一般模式。從程序的角度來看,這就像從未發生錯誤一樣。
不過,如果沒有偵錯工具,則處理架構例外狀況 (包括未處理的頁面錯誤) 的責任就落在使用者模式的監督器身上,而不會由 Zircon 核心處理。舉例來說,如果在受限制模式下執行時,執行緒存取的位址沒有有效對應,系統就會產生 Zircon 例外狀況,並將執行緒切換回一般模式。從 Zircon 例外狀況處理機制的角度來看,返回一般模式會「處理例外狀況」,且不會諮詢其他例外狀況處理程序 (例如執行緒/程序/工作處理程序)。系統會產生標準 zx_exception_report_t
物件並放入模式狀態 VMO,以便在傳回時,使用者模式監督者可取得例外狀況詳細資料。
透過踢出離開
為了支援實作功能,例如優雅的任務終止和 POSIX 信號,我們新增了一個 Zircon 系統呼叫 zx_thread_kick_restricted
,可強制限制模式中的執行緒返回正常模式。踢出是一種鎖定作業,如果執行緒在一般模式下遭到踢出,下次嘗試進入受限模式時,會立即傳回狀態碼,表示遭到「踢出」,ZX_RESTRICTED_REASON_KICK
。傳回這個狀態碼可有效解除鎖定。
踢腳不會「堆疊」,舉例來說,假設目標執行緒處於一般模式,而另一個執行緒發出五次踢出事件。當目標執行緒進入受限模式時,它會立即傳回,有效處理所有五個啟動作業。
一般來說,zx_thread_enter_restricted
的呼叫端應編寫為處理意外的啟動。
元素 2:直接存取
由於作業系統核心經常需要存取使用者模式資料,因此務必確保能有效執行這項作業。通常,作業系統核心會直接存取其使用者程序的程式碼和資料。舉例來說,Linux 核心和 Zircon 核心都能夠直接存取使用者程序的位址空間。
直接存取權是這項設計的關鍵元素。
如前所述,在一般模式下,Zircon 執行緒可存取其程序的完整位址空間,而在受限模式下,則只能存取位址空間的子集。因此,在正常模式下執行時,Zircon 執行緒可充當訪客核心,並可存取位於受限區域的訪客使用者程式碼和資料。
使用者位址空間實際上會分成兩個部分。將位址空間分成兩個部分,我們就能在某些架構上利用一些重要的位址空間切換效率 (即遮罩一半,而非實際切換,TCR_EL1.T0SZ
)。
由於在 Starnix 下執行的程式可能需要其位址空間「靠近」零位址 (例如 MAP_32BIT
),因此我們選擇將最低區域設為受限制的區域。
這種做法有一些限制。主要原因是,由於受限制的區域是完整區域的連續子集,因此不得有任何衝突的對應項目。用於訪客程式碼和資料的位址,必須與用於使用者模式管理員程式碼和資料的位址分開。由於受限制的區域位於使用者位址空間的「下半部」,因此用於實作使用者模式監控器的 Fuchsia 執行階段必須具備彈性,且不要求任何對應項目位於下半部。為此,我們必須對 Fuchsia 程序建構工具進行一些變更。消毒程式有時會對對應項目的位置提出額外要求,因此日後可能需要進行其他工作,才能支援特定消毒程式。
元素 3:共用狀態
核心通常會包含資料結構,這些結構會在使用者工作之間共用,並由系統呼叫操控。對於這類系統,核心程式設計模式類似於多執行緒程式,其中每個使用者工作都是執行緒,可存取共用 (核心) 位址空間,以及存取某些私人、使用者工作專屬的區域。共用區域中的程式碼和資料會形成共用系統映像檔。
為了讓開發人員能以自然的方式,使用共用系統映像檔開發使用者空間核心,我們將擴充 Zircon 程序模型,以支援一種新的程序,讓該程序與其他程序共用部分位址空間。這些程序的集合會同時代管訪客使用者工作和訪客核心。
先前我們說明瞭如何遮蔽程序的位址空間,包括完整和受限。未受限制的區域會由所有 Zircon 程序共用,這些程序會代管相同系統映像檔的來賓工作。每個程序都會有專屬的限制區域,該區域僅供該程序使用。因此,當其中一個程序中的執行緒以一般模式執行時,就會存取整個位址空間,包括共用對應項目以及受限制區域中的私人對應項目。
除了共用這些未受限制的對應項目外,主機代管的 Zircon 程序都會共用單一句柄資料表和單一 futex 情境。共用區域中的任何句柄值 (例如 zx_handle_t
) 都會可供任何程序使用,而無須明確的句柄複製或轉移作業。透過共用 (部分) 對應項目、句柄和 futex 內容,一組程序的樣貌和感受會更像單一程序中的一組執行緒,進而簡化程式設計模型。
在共用或受限制的區域中存取對應項目時,請務必小心,避免建立資料競爭,就像在多執行緒程序中存取記憶體一樣。
雖然共用程序群組中的執行緒具有不同的程序 ID,但它們都會共用相同的句柄資料表、futex 情境和位址空間的共用區域。這種做法的一項影響是,對於 Starnix 而言,一般模式程序的當機情形類似於核心恐慌情形,因為當機程序可能會讓部分共用資料處於不一致的狀態。
當然,嚴格篩選模式的當機情形非常不同,因為嚴格篩選模式無法存取一般模式狀態。由於在一般模式下執行的程式碼會擔任受限模式程式碼的監督者,因此在大多數情況下 (例如 Starnix),系統應能妥善處理受限模式的「當機」問題,例如架構例外狀況和未處理的頁面錯誤。
實作
我們一開始先建構 x86 支援,因為 Starnix 最初的目標就是 x86。後來我們新增了 arm64 和 riscv64 支援功能。
雖然設計和實作著重於 Starnix 用途,但功能的開發方式可讓這些功能完全不受 Starnix 影響,且可在 Starnix 之外使用 (及測試)。
@next
vDSO 已新增系統呼叫。
Syscall 介面和語意
程序建立
為了進入受限制模式,執行緒必須是「共用程序」的一部分,並具有受限制的位址空間區域。共用程序可選擇與群組中的其他程序共用部分位址空間 (共用區域)、句柄資料表和 futex 內容。我們說「可選」是因為共用程序可在單一群組中存在。
建立程序時,系統會判斷該程序是一般程序還是共用程序。為了建立共用程序,我們擴充現有的 zx_process_create
系統呼叫,並引入新的 zx_process_create_shared
。
現有的 zx_process_create
系統呼叫已變更為接受新的選項 ZX_PROCESS_SHARED
。出現時,產生的程序位址空間 (由傳回的 VMAR 模擬) 只會涵蓋共用區域 (上半部)。使用 ZX_PROCESS_SHARED
建立的程序會做為原型,用於建立一組共用部分位址空間的程序。
新增的系統呼叫 zx_process_create_shared
已新增,並會與 ZX_PROCESS_SHARED
選項搭配運作。
zx_process_create_shared(zx_handle_t shared_proc,
uint32_t options,
const char* name,
size_t name_size,
zx_handle_t* proc_handle,
zx_handle_t* restricted_vmar_handle);
這個新呼叫與 zx_process_create
類似。透過 zx_process_create_shared
建立程序的能力,受呼叫端的工作政策和 shared_proc
引數的規範。
zx_process_create_shared
會建立新程序 proc_handle
和新 VMAR restricted_vmar_handle
。新程序的位址空間包含兩個區域:共用和私人,並由兩個不同的 VMAR 支援。共用區域由先前 ZX_PROCESS_SHARED
建立呼叫傳回的現有 VMAR 支援。私人區域由新建立的 VMAR 支援,並透過 restricted_vmar_handle
傳回。
如此一來,Starnix 執行元件就能建立 Starnix 程序集合,這些程序會共用一半的位址空間對應項目,但每個程序都有獨立的下半部,可代管受限制模式的程式碼和資料。
進入/退出嚴格篩選模式
只有設有限制區域的共用程序中的執行緒可以進入限制模式。
進入前,執行緒必須使用 zx_thread_prepare_restricted
建立並繫結模式狀態 VMO,以便進行準備。執行緒和 Zircon 核心會使用模式狀態 VMO,以便儲存/還原受限模式的通用註冊狀態。執行緒 (以及任何明確建立的使用者對應項目) 會保留 VMO,直到執行緒結束為止。
準備呼叫成功後,執行緒可能會使用 zx_thread_enter_restricted
進入受限模式。離開受限模式後,執行緒可以重新進入,而無需重新準備,無論是離開 ZX_RESTRICTED_REASON_SYSCALL
、ZX_RESTRICTED_REASON_EXCEPTION
或 ZX_RESTRICTED_REASON_KICK
(zx_thread_kick_restricted
) 皆然。
準備
zx_status_t zx_thread_prepare_restricted(uint32_t options, zx_handle_t* out_vmo);
Prepare 會建立並將模式狀態 VMO 繫結至目前的執行緒。產生的 VMO 會用於在進入/離開嚴格篩選模式時儲存/還原嚴格篩選模式狀態。
傳回的 VMO 與使用 zx_vmo_create
建立的 VMO 類似,而產生的句柄具有與 zx_vmo_create
建立的 VMO 相同的權限,且沒有任何選項。請注意,系統不支援解除提交、調整大小或建立子 VMOs。支援透過 zx_vmo_read
/zx_vmo_write
進行對應、取消對應,以及讀取/寫入。
一次只能將一個模式狀態 VMO 繫結至一個執行緒。嘗試繫結另一個 VMO 會取代已繫結的 VMO。只有在最後一個使用者句柄關閉、最後一個使用者對應項目移除,且該項目已由另一個對 zx_thread_restricted_prepare
的呼叫取代或執行緒終止後,系統才會銷毀已繫結的 VMO。與其他 VMO 一樣,一旦 VMO 對應完成,就會保留其對應項目,因此呼叫端可以關閉句柄,並透過對應項目直接存取記憶體。
呼叫端的工作政策必須允許 ZX_POL_NEW_VMO
。
這個呼叫可能會失敗,原因如下:
ZX_ERR_INVALID_ARGS
:out_vmo
是無效的指標或 NULL,或是options
是 0 以外的任何值。ZX_ERR_NO_MEMORY
- 記憶體不足導致失敗。
注意:如果
out_vmo
是無效的指標,則即使呼叫傳回ZX_ERR_INVALID_ARGS
,新建立的 VMO 的句柄也可能仍會繫結至執行緒。系統會產生政策例外狀況 (ZX_EXCP_POLICY_CODE_HANDLE_LEAK
)。假設已處理例外狀況,呼叫端可以使用有效的out_vmo
再次呼叫準備作業,從這個狀態復原。
輸入
status_t zx_thread_enter_restricted(uint32_t options,
uintptr_t return_vector,
uintptr_t context);
將呼叫執行緒的可存取位址空間變更為共用程序的限制區域,並載入模式切換器 VMO 中包含的註冊狀態,即可進入限制模式。在呼叫之前,執行緒必須使用 zx_thread_prepare_restricted
準備模式狀態 VMO,並在偏移量為零的位置填入 zx_restricted_state_t
結構體。
與一般函式呼叫不同,成功的 zx_thread_enter_restricted
呼叫不會傳回至呼叫內容。相反地,在離開受限模式後,執行緒會直接跳至 return_vector
中指定的位址,並在兩個通用目的地註冊中載入 context
和原因代碼。所有其他暫存器 (包括堆疊指標) 都會處於未指定狀態。return_vector
中的一般模式程式碼負責使用 context
、原因代碼和模式狀態 VMO 的內容,重建系統呼叫前狀態。
可能的理由代碼為 ZX_RESTRICTED_REASON_SYSCALL
、ZX_RESTRICTED_REASON_EXCEPTION
和 ZX_RESTRICTED_REASON_KICK
。返回時,模式狀態 VMO 的偏移量 0 會依據原因代碼,包含下列其中一個結構體:zx_restricted_syscall_t
、zx_restricted_exception_t
或 zx_restricted_kick_t
。
在 x64 上,context
會放入 rdi
,而原因代碼則會放入 rsi
。
在 arm64 上,context
會放置在 x0
,而原因代碼則會放置在 x1
。
在 riscv64 上,context
會放置在 a0
,而原因代碼則會放置在 a1
。
失敗的呼叫可能會傳回:
ZX_ERR_INVALID_ARGS
:return_vector
不是有效的使用者位址,或選項無效。ZX_ERR_BAD_STATE
- 模式狀態 VMO 中的受限模式註冊狀態無效,或是沒有與呼叫執行緒繫結的模式狀態 VMO。
結構體的定義如下:
typedef struct zx_restricted_syscall {
zx_restricted_state_t state;
} zx_restricted_syscall_t;
typedef struct zx_restricted_exception {
zx_restricted_state_t state;
zx_exception_report_t exception;
} zx_restricted_exception_t;
typedef struct zx_restricted_kick {
zx_restricted_state_t state;
} zx_restricted_kick_t;
請注意,所有三個結構體的第一個元素都是 zx_restricted_state_t
物件。傳回時,此狀態物件會包含執行緒返回一般模式時的受限模式通用註冊狀態。儲存任何其他受限制的註冊狀態 (例如偵錯或 FPU/向量註冊) 是 return_vector
的一般模式的責任。
嚴格篩選模式的通用寄存器狀態 zx_restricted_state_t
的定義如下:
在 arm64 上,
typedef struct zx_restricted_state {
uint64_t x[31];
uint64_t sp;
uint64_t pc;
uint64_t tpidr_el0;
// Contains only the user-controllable upper 4-bits (NZCV).
uint32_t cpsr;
uint8_t padding1[4];
} zx_restricted_state_t;
在 x64 上,
typedef struct zx_restricted_state {
uint64_t rdi, rsi, rbp, rbx, rdx, rcx, rax, rsp;
uint64_t r8, r9, r10, r11, r12, r13, r14, r15;
uint64_t ip, flags;
uint64_t fs_base, gs_base;
} zx_restricted_state_t;
在 riscv64 上,它只是 zx_riscv64_thread_state_general_regs_t
的 typedef。
typedef zx_riscv64_thread_state_general_regs_t zx_restricted_state_t;
其中 zx_riscv64_thread_state_general_regs_t
是
typedef struct zx_riscv64_thread_state_general_regs {
uint64_t pc;
uint64_t ra; // x1
uint64_t sp; // x2
uint64_t gp; // x3
uint64_t tp; // x4
uint64_t t0; // x5
uint64_t t1; // x6
uint64_t t2; // x7
uint64_t s0; // x8
uint64_t s1; // x9
uint64_t a0; // x10
uint64_t a1; // x11
uint64_t a2; // x12
uint64_t a3; // x13
uint64_t a4; // x14
uint64_t a5; // x15
uint64_t a6; // x16
uint64_t a7; // x17
uint64_t s2; // x18
uint64_t s3; // x19
uint64_t s4; // x20
uint64_t s5; // x21
uint64_t s6; // x22
uint64_t s7; // x23
uint64_t s8; // x24
uint64_t s9; // x25
uint64_t s10; // x26
uint64_t s11; // x27
uint64_t t3; // x28
uint64_t t4; // x29
uint64_t t5; // x30
uint64_t t6; // x31
} zx_riscv64_thread_state_general_regs_t;
踢
zx_status_t zx_thread_kick_restricted(zx_handle_t thread, uint32_t options);
如果執行緒目前處於受限模式,則會將其踢出受限模式;如果不是,則會儲存待處理的踢出作業。如果目標執行緒在受限制模式下執行,則會透過提供給 zx_thread_enter_restricted
的 return_vector
退出至一般模式,且原因代碼設為 ZX_RESTRICTED_REASON_KICK
。否則,下一次對 zx_thread_enter_restricted
的呼叫將不會進入受限制模式,而是會以原因代碼 ZX_RESTRICTED_REASON_KICK
調度至提供的進入點。
同一個執行緒物件上的多個啟動事件會一併摺疊。因此,如果多個執行緒在同一個目標執行或進入受限模式時呼叫 zx_thread_kick_restricted
,則會觀察到至少一個,但可能會有多個 ZX_RESTRICTED_REASON_KICK
傳回值。建議使用此系統呼叫的方法,是先記錄同步資料結構的啟動原因,然後再呼叫 zx_thread_kick_restricted
。呼叫 zx_thread_enter_restricted
的執行緒應在觀察 ZX_RESTRICTED_REASON_KICK
時查詢此資料結構,並在重新進入受限模式前處理任何待處理狀態。
thread
句柄必須具有正確的 ZX_RIGHT_MANAGE_THREAD
。
這個呼叫可能會失敗:
ZX_ERR_WRONG_TYPE
-thread
不是執行緒。ZX_ERR_ACCESS_DENIED
-thread
沒有 ZX_RIGHT_MANAGE_THREAD。ZX_ERR_BAD_STATE
-thread
已死。
其他系統呼叫變更
VMAR 導向的系統呼叫 - zx_vmar_map
和 zx_vmar_op_range
等系統呼叫不受此提案影響。雖然 zx_vmar_root_self
不是系統呼叫,但會受到這項提案的影響。詳情請參閱後續章節的討論內容。
zx_process_read_memory
和 zx_process_write_memory
:這些系統呼叫會取得程序句柄,並提供對程序記憶體的存取權。目前 (在本提案提出之前),這些呼叫不會跨越對應邊界。也就是說,如果您要求讀取跨越兩個對應項目的 8 KiB 區域,您只會收到前 4 KiB。根據這項提案,這些呼叫的 API 和語意不會變更。您需要對實作方式進行小幅變更,才能根據提供的 vaddr
對適當的 VmAspace
(共用或私人) 執行 FindRegion
呼叫。呼叫端仍可讀取及寫入程序的完整位址空間,包括共用和私人區域。
ZX_INFO_TASK_STATS
和 ZX_INFO_PROCESS_MAPS
:實作項目已更新,以便納入一般模式和受限模式對應項目,但 API 仍維持不變。也就是說,結果不會區分正常和受限。
其他變更
zx_vmar_root_self
- 此提案的最大語意變更之一,是透過 zx_process_create_shared
建立的程序不會有單一根 VMAR 來封裝整個位址空間。而是會擁有兩個 VMAR。一個用於共用區域,另一個用於私人區域。語意變更是否會向程式公開,以及公開方式,則由語言執行階段決定。zx_vmar_root_self
用於 C、C++ 和 Rust 執行階段。根據這項提案,zx_vmar_root_self
的行為更像是「取得預設 VMAR」,會傳回共用 VMAR,讓現有程式庫 (例如 scudo) 在「預設」共用區域中運作。
zx_process_self
:由於 zx_process_self
會傳回全域變數的值,且在共用區域支援全域變數的共用程序中,它實際上並不會傳回自身的句柄。而是傳回群組中第一個共用程序 (原型程序) 的句柄。我們曾考慮將 zx_process_self
變更為使用執行緒本機儲存空間,而非全域變數,但最終決定暫時擱置 (請參閱 https://fxbug.dev/352827964),改為變更 zx_process_self
的錯誤用途。詳情請參閱下文。
pthread_create
:這是 zx_process_self
最有趣的使用問題。為確保 pthread_create
在正確的程序中建立執行緒,它需要呼叫程序的控制代碼。為解決這個問題,我們擴充 pthread
結構體,以納入程序句柄,並變更 pthread_create
,以便使用結構體中的句柄,而非 zx_process_self
傳回的句柄。
偵錯工具:偵錯工具 (例如 zxdb 和 fidlcat) 可能需要變更,才能瞭解執行緒的所有活動對應項目不一定與其程序中的所有執行緒相同。換句話說,我們可能需要為偵錯工具建立一種方法,讓偵錯工具查詢已暫停執行緒的位址空間。
Fuchsia 程序建構工具:我們已變更部分假設,針對可存取的程序位址空間部分,因此必須對程序建構工具進行些微變更。舉例來說,原型製作程序不會有下半部。
啟動 Starnix 元件 - Starnix 元件會由 Component Manager 啟動。如同任何 ELF 元件,zx_process_create
用於在 galaxy 中建立第一個程序。與其他 ELF 元件不同,第一個 Starnix 程序的 VMAR 必須是共用 VMAR。為此,我們定義了新的元件資訊清單選項 is_shared_process
,用於指示元件管理服務傳遞 ZX_PROCESS_SHARED
。這個新程序會做為原型,負責在銀河系中建立後續程序。這個元件資訊清單選項可盡量減少元件管理服務的變更,同時避免 Starnix 需要進入 Fuchsia 程序建構作業。原型建立的每個程序都會直接共用原型的程式碼、資料和句柄。
成效
對現有系統呼叫的影響最小
對某些系統呼叫可能會有小幅效能影響,但不會為零。我們會使用 Zircon 微型基準測試套件,確保任何影響都微乎其微。除了影響執行階段效能之外,某些核心物件可能會略微增加,導致記憶體用量略微增加。如同執行階段效能,我們預期記憶體用量會略微增加,但會進行測量和驗證。
模式切換效能
限制模式的模式切換效能會比原生模式切換效能更耗費資源,因為 CPU 在正常模式和限制模式之間切換時,實際上必須經過 EL1。不過,我們認為這筆費用在 Starnix 的情況下不會太高。
我們將新增 Zircon 微基準測試,以便評估及追蹤模式切換成本。
安全性考量
這個機制旨在充當安全界線,因此需要由 Fuchsia 安全團隊進行徹底審查。
一般
使用這些新的 Zircon 設施的使用者模式程式本身會充當核心,因此必須小心保護自己的資料和執行作業,以免受到以受限模式執行的程式碼影響,這與 Zircon 核心保護錯誤或可能有害的 Fuchsia 使用者模式程式碼類似。
雖然本文件其他部分已詳細討論這項功能,但仍值得強調一些重點:
- 共用句柄:共用程序群組中的所有程序都會使用單一句柄表,因此在群組中任何程序中以一般模式執行的任何執行緒,都可能會使用表中的任何句柄。
- 共用對應項目:共用程序群組中的所有程序會共用部分的位址空間 (共用 VMAR)。在正常模式下執行時,這個共用區域中的對應項目可供群組中的所有執行緒存取。
- 在一般模式中可存取受限制的區域 - 在一般模式中執行時,可存取程序的受限制區域。由於這個區域包含不受信任的程式碼和資料,因此請務必小心,絕對不要信任受限制區域中的任何程式碼或資料 (例如複製並驗證,以免發生 TOCTOU)。
- 必須在進入時清除/還原 - 進入限制模式時,程式必須小心清除、還原或以其他方式取代
zx_restricted_state_t
指定的註冊狀態。包括浮點、向量和偵錯暫存器狀態。否則可能會將資料洩漏給不受信任的程式碼,甚至造成更嚴重的後果。 - 必須在傳回時清除/還原 - 當程式返回一般模式時,必須小心清除、還原或以其他方式取代
zx_restricted_state_t
指定的註冊狀態。否則,未信任的程式碼可能會影響或控制使用者模式監督程式的執行,導致逃逸。
推測執行漏洞
執行不受信任的來賓程式的使用者模式核心,需要像 Zircon 核心一樣防範推測執行攻擊。不過,由於每次從受限模式切換至該模式或從該模式切換至該模式,都必須經過 Zircon 核心,因此我們有理由讓訪客核心更簡單,並可能不受某些推測執行漏洞的影響。這個想法是,客體核心可以依賴 Zircon 核心在核心進入點和結束點執行的推測執行緩解措施。當然,Fuchsia 安全團隊需要審查這項策略,而每個新發現的推測執行安全漏洞,都需要讓客體核心的擁有者瞭解。
存取權受限
在受限制模式下,執行緒只能存取受限制的位址空間,且無法發出 Zircon 系統呼叫。有兩種機制可防止受限模式執行緒發出 Zircon 系統呼叫。首先,系統會將所有系統呼叫導向至一般模式。其次,即使系統呼叫已路由至 Zircon 核心,但受限區域缺少 Zircon vDSO,因此無法提供服務。
值得一提的是,在進入受限模式之前,呼叫端應小心,不要透過註冊表「進入」受限模式,而意外洩漏資訊。
當然,如果受限模式的程式碼能夠以某種方式危害同個程序中的一般模式程式碼 (例如透過一般模式程式碼中的錯誤),那麼它也可能危害所有形成共用系統映像的 Zircon 程序,因為這些程序都會透過共用對應項目共用部分程式碼和資料。
監控模式存取權和執行保護
x86 的 SMAP 和 SMEP,以及 arm64 的 PAN 和 PXN 是硬體功能,可限制核心程式碼可存取或執行的對應項目。這類功能的設計目的是讓人更難利用核心錯誤。由於受限模式是軟體建構,且訪客核心會在使用者模式下執行,因此我們無法在使用者模式的監督器中直接運用這些功能。另請參閱「替代方案」一節中關於替代對應的討論。
隱私權注意事項
這些新的核心功能本身並未考量隱私權問題,這點與其他核心功能並無二致。不過,如果要將這些功能應用於在以 Fuchsia 為基礎的產品上執行 Linux 二進位檔,日後可能需要進行隱私權審查。
一旦我們有涉及 Linux 二進位檔的特定端對端產品用途,就必須評估該用途的隱私權影響。
未來方向
這項設計的幾個面向可能會隨著時間而變更。本節將說明我們近期可能會重新審視的部分內容。後續文件會進一步說明這些概念。
API 分解
這項提案整合了 API 後端的多項功能,並著重於 Starnix 用途。舉例來說,在目前的形式中,執行緒必須位於共用程序中,才能限制其位址空間。雖然這對 Starnix 來說沒問題,但這種耦合會限制此系統的適用性。一旦我們有更多這項技術的用途,就應嘗試將 API 和功能組合分解為更多獨立的構件。
一個根 VMAR
關於分解的註解,我們應在日後的提案中重新考慮「程序可能有許多根 VMAR」的概念。在目前的提案中,共用和私人 VMAR 都視為根 VMAR,因為兩者都未包含在其他 VMAR 中。這種多根方法是實用設計決策,靈感來自 VmAspace
、VmAddressRegion
和 VmMapping
類別的現有實作。重新設計/重構這些類別,即可避免程序需要有多個根 VMAR (但我們可能會基於其他原因支援多個根)。
改善註冊儲存功能
我們可能需要改善的部分,是如何在進入/離開限制模式時,儲存、還原或歸零 FPU/向量註冊狀態。
Starnix 程式碼或其中一個相依項目可能會使用 FPU/向量註冊 (請參閱 memcpy),因此 Starnix 必須小心保存/還原受限制的 FPU/向量狀態。目前,Starnix 會在每個受限制的進入點和離開點儲存/還原所有 FPU/向量狀態。為了提升效能,我們可能會編譯在受限制的結束點上執行的部分 Starnix 邏輯,以便延遲或減少需要儲存的暫存器組合。
雖然 Starnix 會小心保存/還原 FPU/向量註冊器狀態,但從技術層面來說,Zircon vDSO 可能會覆寫呼叫端在進入受限模式前建立的狀態 (請參閱 https://fxbug.dev/42062491)。目前透過驗證受限模式 FPU/向量註冊狀態的測試,可減輕這種脆弱性。
此外,在某些情況下,我們可以提供特殊的系統呼叫,利用某些架構提供的特權儲存/還原指示,或許還可以利用硬體輔助的延遲儲存/還原方案。
測試
如同核心介面中的其他部分,我們會實作核心測試和核心內單元測試,以驗證正確性。系統會使用 Zircon 微基準測試來評估效能。
說明文件
我們會新增/更新新系統呼叫和修改過的系統呼叫的樹狀結構說明文件。
我們會更新 Zircon 例外狀況的樹狀結構說明文件。
我們會撰寫說明這項新機制的樹狀結構內總覽文件。
缺點、替代方案和未知事項
缺點
這些變更會影響多個核心元件,並增加核心的複雜度。雖然設計會盡力將此新複雜性分割並降到最低,但這仍會造成成本。
替代方案
HyperVisor:除了這項僅限軟體的設計外,您也可以利用硬體的 HyperVisor 功能,讓「Guest Kernel」直接發出 Zircon 系統呼叫。有點像是極端版本的準虛擬化。詳情請參閱 RFC:虛擬化的直接模式。採用僅使用軟體的方法的原因之一,是為了確保我們可以在無法存取硬體輔助虛擬化的架構和/或裝置上套用這項方法。
整合為單一程序:這項設計會為每個客體程序提供一個 Zircon 程序。這種做法的好處之一,就是每個來賓程序都會「顯示」給 Zircon 工具 (例如 ps
或 kill
)。另一種設計會將每個來賓程序模擬為單一 Zircon 程序中的執行緒集合。這個替代設計會「隱藏」訪客程序,不讓其出現在 Zircon 使用者模式的其他部分,但其他方面並無太大差異。
Supervisor-supervisor 隔離:這個設計的替代方案是移除共用區域,並要求每個 supervisor 程序透過 IPC 或某些「手動管理」的明確對應關係彼此協調。這個替代方案的好處是,使用一個程序時,較難影響另一個程序 (改善隔離性),但會使實作訪客核心的許多層面更為困難 (請參閱「目標」一節的「自然且簡化的程式設計模式」)。跨程序的協調額外負擔也可能會影響效能。
在不同程序中執行訪客和監督者:除了引入新模式,您也可以將不受信任的程式碼放入沒有 Zircon vDSO 的獨立程序中。此時,訪客系統呼叫會包含從不受信任的程序切換至實作訪客核心的 Zircon 程序的內容切換。這種方法會正常運作,但可能需要為每個系統呼叫提供「更完整」的內容切換,且在執行階段效能方面成本較高。此外,這可能會導致每個訪客工作需要兩個 Zircon 執行緒,而非受限模式的每個訪客工作只需要一個 Zircon 執行緒 (一個 Zircon 執行緒用於執行訪客工作,另一個則用於代為實作系統呼叫)。與受限模式相比,針對不受信任的程式碼/資料使用個別程序,在 IPC 和訪客與監督者之間的內容切換方面,會有一些效能上的缺點。
專用讀取/寫入與模式狀態 VMO - 這項設計的早期版本使用專用系統呼叫,用於存取模式狀態 (zx_restricted_state_read
和 zx_restricted_state_write
)。不過,這些系統呼叫很快就變成效能問題,因此我們將其淘汰,改用模式狀態 VMO。
Unbind - 在先前的疊代中,有一個明確的 unbind 系統呼叫,會「復原」由 zx_thread_prepare_restricted
執行的繫結作業。我們發現 unbind 並非必要,且只用於部分測試程式碼。我們已將其移除,以簡化 API。
存取和執行保護的替代對應項目 - 使用者模式監督器可用的漏洞緩解工具組合,與核心模式監督器可用的工具組合不同。一般來說,兩者都能使用各種軟體技術 (例如 CFI),但使用者模式監控器無法使用 x86 的 SMAP 或 SMEP,或 arm64 的 PAN 或 PXN 等硬體功能。這些硬體功能可讓您在執行核心模式時,標示可在不同情境中存取哪些對應項目。有幾個 SMEP/PXN 和 SMAP/PAN 的潛在替代方案,可視需要進一步探索。這項功能的概念是維持一組具有不同保護位元的替代對應項目,並在切換一般模式和限制模式,或在共用區域和限制區域之間執行「使用者複製」作業時,在這些對應項目之間進行轉換。當然,維護和使用替代對應項目會產生一些重大的效能和複雜性成本,因此這個替代方案不一定可行。
未知
核心核心物件關係的變更 - 此提案會變更一些核心核心物件關係。舉例來說,它會在句柄表和程序之間建立一對多關係,程序不再只有一個位址空間 (或根 VMAR),futex 可在程序之間共用,特定句柄可能同時「存在於多個程序」等等。這些變更可能會造成潛在的設計缺陷。
偵錯和診斷 - 這項提案會影響偵錯工具的模型,以及其他以處理程序為中心的工具,例如記憶體監控器 (fx mem
)。我們需要與 zxdb 和記憶體監控器的擁有者合作,仔細思考相關影響。相關內容,我們需要處理現有系統呼叫的詳細資料,這些系統呼叫假設程序只有一個有效的位址空間 (例如 zx_process_read_memory
)
既有技術與參考資料
請參閱 RFC-0082:在 Fuchsia 上執行未經修改的 Linux 程式,以及其「先前技術和參考資料」一節。
這項設計是根據多份 Google 內部文件和大量對話所設計。另請參閱
- go/zx-unified-aspace
- go/zx-restricted-exceptions
- go/zx-restricted-kick
- go/zx-restricted-roadmap
- go/zx-starnix-faults
- go/zx-starnix-state-transitions
- go/zx-multi-aspace-processes
- go/zx-restricted-mode
嚴格篩選模式的靈感來自 ARMv8-A 例外狀況層級 (「如果我們有 EL-minus-1 怎麼辦?」)。