| RFC-0143:使用者空間頂端位元組忽略 | |
|---|---|
| 狀態 | 已接受 |
| 區域 |
|
| 說明 | 變更核心 ABI,以支援標記的使用者空間指標。 |
| Gerrit 變更 | |
| 作者 | |
| 審查人員 | |
| 提交日期 (年-月-日) | 2021-11-30 |
| 審查日期 (年-月-日) | 2021-11-30 |
摘要
本文提議變更核心 ABI,以支援標記的使用者空間指標。
提振精神
Top-Byte-Ignore (TBI) 是所有 ARMv8.0 CPU 的一項功能,可讓虛擬位址的最高位元組在載入和儲存時遭到忽略。而是先將位元 55 擴展至位元 56-63,再進行位址轉譯。這項功能可讓您將 (忽略的) 頂端位元組做為標記,或用於其他頻內中繼資料。TBI 的其中一項立即用途,是在使用者空間中啟用 Hardware-assisted AddressSanitizer (HWASan),將標記儲存在頂端位元組中,以追蹤記憶體。
本文說明核心應如何處理已加上標記的使用者指標。
雖然 TBI 和 HWASan 是標記指標最相關的用途,但這項設計並非僅涵蓋這些用途。其他平台也有類似的硬體功能,支援標記指標,而其他使用者空間程式則可將這些標記位元用於自身特定用途。這項設計應具備足夠的通用性,可支援其他標記指標的實作項目,且不會特別著重於某個使用者應用程式。
術語
以下是本提案中經常使用的詞彙。有地址,也有指標。這兩個概念類似,但處理方式大相逕庭。在語意上,有些系統呼叫會對位址執行作業,有些則會對指標執行作業。
地址
地址是 64 位元整數,代表使用者位址空間界限內的位置。地址絕不會加上標記。操控位址空間的系統呼叫會對位址進行操作。地址的值一律會限制在地址空間範圍內 (以 ZX_INFO_VMAR 表示)。
指標
指標是程式設計語言專屬的概念,通常表示可取值的記憶體位置。每種語言都會定義指標的實作方式,以及如何轉換為硬體。以 C/C++ 來說,在 HWASan 的環境中,指標是 64 位元整數,由標記位元和位址位元組成。指標可以標記 (表示非零標記位元) 或未標記 (表示零標記位元)。存取使用者記憶體的系統呼叫通常會對指標執行作業。
標記
標記是指指標的上層位元,通常用於中繼資料。在啟用 TBI 的 ARM 上,標記寬度為 8 位元,由位元 56 到 63 組成。
TBI (Top-Bits-Ignore)
其他潛在的硬體功能 (例如 ARM MTE 或 Intel LAM) 也支援「忽略」指標部分高位元的方式。除非另有規定,否則本文中使用的「TBI」一詞代表支援忽略標記的任何一般硬體功能,而非僅限於 ARM TBI。
設計
核心程式碼應複製硬體的行為。應妥善處理標記,確保核心行為對使用者有意義。
啟用 TBI 後,系統應有的行為如下:
zx_channel_write可以接受已標記的指標,且呼叫行為與指標未標記時相同。當程序在已標記的使用者指標上發生頁面錯誤時,系統會解決頁面錯誤,就像錯誤發生在同一個未標記的指標上,但有一個例外。如果故障產生 Zircon 例外狀況,例外狀況報告的故障指標會包含原始標記的指標,具體程度取決於硬體保留的指標。
為解決 futex 喚醒/等待問題,系統會忽略所提供指標上的任何標記。換句話說,如果指標只因標記而異,喚醒指標仍會喚醒等待該指標的所有等待者,無論他們指定的標記為何。
從程序讀取記憶體時 (例如使用
zx_process_read_memory),核心會接受位址做為要讀取記憶體區塊位置的引數。除了軟體偵錯,偵錯工具還需要將偵錯目標指標值明確轉換為位址,才能透過核心 API 讀取。
已標記指標 ABI:不區分標記,但會保留標記
啟用 TBI 後,系統會套用下列設定:
核心會忽略從系統呼叫接收的使用者指標上的標記。舉例來說,如果緩衝區指標包含標記,
zx_channel_read呼叫的行為與緩衝區指標未標記時完全相同。在接受位址的系統呼叫 (即
zx_vaddr_t)。舉例來說,傳遞至zx_process_read_memory的虛擬地址無法加上標記。在需要位址的地方使用已標記的指標,會被視為任何其他無效位址。當核心接受已標記的指標時 (無論是透過系統呼叫或錯誤),都會盡量保留標記,以供使用者程式碼稍後觀察。舉例來說,如果使用者在標記的使用者指標上發生程式錯誤,且硬體可以保留標記,則產生的 Zircon 例外狀況報告會包含該標記。如果硬體無法保證標記會保留,系統就會移除標記。如果使用者空間無法觀察標記,只要不改變系統行為,核心就能自由移除標記。如果硬體僅保證部分保留標記,核心可能只會移除未保證保留的位元。
核心本身絕不會產生標記指標。舉例來說,當您透過
zx_vmar_map對應 VMO 時,核心選取的結果值會是沒有標記的純位址。比較使用者空間指標時,核心會忽略可能存在的任何標記。舉例來說,如果某個執行緒正在等待 (透過
zx_futex_wait) 標記為 A 的指標,而另一個執行緒正在喚醒 (透過zx_futex_wake) 具有相同位址位元但標記為 B 的指標,則等待者會被喚醒。
已為「所有內容」啟用 ARM64 TBI
TBI 將由核心啟動選項控制。啟用後,所有使用者空間程序都會開啟 TBI。
偵錯軟體
偵錯工具必須能辨識 TBI。ARM TBI 不允許在偵錯暫存器上設定標記。偵錯工具必須先明確地將最重要的 VA 位元符號擴充,才能設定偵錯暫存器。
實作
開機選項會控管使用者位址空間是否已啟用 ARM TBI。
如要啟用 ARM TBI,請在轉譯控制暫存器 (EL1) 中設定 TBI0 和 TBI1 位元。
除了啟用/停用 TBI 之外,我們還需要確保現有的系統呼叫可正確處理指標/位址。核心處理使用者指標 (例如 user_ptr) 的位置不多,因此實作這項提案所需的變更相對較少。
我們可以透過新的系統功能,向使用者空間指出執行的 TBI 類型。我們可以導入新的功能種類 ZX_FEATURE_KIND_ADDRESS_TAGGING,而這個種類可以支援新的功能位元,指出位址標記,例如 ARM TBI 的 ZX_ARM64_FEATURE_ADDRESS_TAGGING_TBI。
效能
對效能的影響應該微乎其微,現有的微基準將用於驗證。
測試
我們需要測試以下項目:
檢查使用指標的系統呼叫,這些指標具有不同的標記,而這些標記實際上會遭到忽略。
在已標記與未標記的指標上喚醒 (標記不區分大小寫的行為)。
在標記指標上發生錯誤時,系統會將標記保留在例外狀況中 (保留標記的行為)。
任何讓使用者空間瞭解核心 TBI 的行為,例如系統功能的存在,或是傳回忽略位元數的查詢。
驗證標記指標是否遭預期位址的系統呼叫拒絕。
如果系統不支援 TBI,就必須略過這些測試。
說明文件
這份 RFC 記錄了所有有關標記指標 ABI 的說明文件。實作完成後,我們可能需要更新部分 Zircon 文件,指明哪些系統呼叫的引數無法接受標記。
保證保留某種程度標記的系統呼叫,需要記錄下來,以指定保留哪些位元,以及可以剝除哪些位元。
缺點、替代方案和未知事項
TBI 切換精細程度
我們有兩個層級可以控制 TBI 的範圍:全域和每個程序。如果採用以程序為單位的做法,則需要某種機制,允許在程序建立或啟動時切換 TBI。這需要新的系統呼叫、引數或位元旗標,因此需要更多測試,且可能會產生新的錯誤或安全性問題,需要時間才能發現。擁有程序切換開關可能所費不貲。
全域切換較不複雜,且有助於避免許多「未知未知」的情況,不必實作執行階段切換。如果系統的所有應用程式都支援或不支援 TBI,而非兩者混用,安全性也可能更高。
在使用者模式中移除標記
這包括在所有標記進入核心前,先在系統呼叫層中移除標記。這樣一來,就不需要變更核心,核心也不會受到標記影響。其中一個問題是使用者空間指標的錯誤處理。如果標記的指標產生錯誤,則每個使用者空間處理常式都必須移除標記。
支援其他定址模式
這項提案應具備足夠的彈性,可說明涉及「忽略」最高位元的其他硬體功能。我們近期不打算支援這些功能,但應該會處於開啟其中一項功能只需進行少量變更的狀態。
ARM Memory Tagging Extension (MTE)
MTE 是一項功能,可與 TBI 搭配運作,找出錯誤的記憶體存取。記憶體標記會將每個配置和指標與特定標記值建立關聯。如果指標的標記與嘗試存取的分配項目不同,表示標記不符,因此記憶體存取有誤。使用 MTE 時,這個標記是儲存在指標頂端位元組中的 4 位元值。
根據這項 ABI,如果啟用 MTE,標記會參照指標的前 8 位元,但由於硬體只保證保留這 4 位元,因此只會保留位元 56 到 59 的錯誤。
Intel 線性位址遮罩 (LAM)
LAM 是 x86 即將推出的功能,可遮蓋指標中前 7 或 16 個位元 (視情況而定),以進行載入/儲存作業。這項功能由 CR3 暫存器控制,與 TBI 不同,LAM 不會在頁面錯誤時保留任何標記位元。
既有技術和參考資料
AArch64 Linux 中的標記虛擬位址
這項提案的許多設計靈感來自 Linux 的標記位址 ABI,也就是說,接受標記指標時,大多數核心行為應不受影響。其中一個主要差異是,Linux 支援切換每個執行緒的 ABI,而這項提案的目標是在建構/啟動時全域切換 ABI。此外,ARM TBI 一律會在 Linux 上啟用,而 ARM TBI 也由相同的建構選項控管。