RFC-0143:Userspace Top-Byte-Ignore | |
---|---|
狀態 | 已接受 |
區域 |
|
說明 | 變更核心 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
表示)。
指標
指標是特定程式設計語言的概念,通常會指出可解析記憶體的位置。每種語言都會定義指標的實作方式,以及將指標轉譯為硬體的方式。在 HWASan 的環境中,C/C++ 的指標是 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 時,核心選取的結果值將是沒有標記的純地址。比較使用者空間指標時,核心會忽略任何可能存在的標記。舉例來說,如果執行緒在標記為 A 的指標上等待 (透過
zx_futex_wait
),而另一個執行緒在標記為 B 的相同位址位元的指標上喚醒 (透過zx_futex_wake
),則等待者會被喚醒。
為 Everything 啟用 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,則必須略過這些測試。
說明文件
標記指標 ABI 的所有說明文件皆記載於此 RFC 中。實作完成後,我們可能需要更新部分 Zircon 說明文件,以指定哪些系統呼叫的引數無法接受標記。
需要記錄可確保一定程度的標記保留作業系統呼叫,以便指定要保留哪些位元,以及哪些位元可移除。
缺點、替代方案和未知事項
TBI 切換精細程度
我們可以透過兩種層級控制 TBI 的範圍:全域和個別程序。個別程序方法會涉及某些機制,可在程序建立時間或啟動時間切換 TBI。這需要新的系統呼叫、引數或位元旗,而這些都需要更多測試,且可能會引入新的錯誤或安全性問題,需要花時間才能發現。使用程序切換鈕可能會耗費資源。
全域切換鈕較不複雜,有助於避免實作執行階段切換鈕時產生的許多「未知未知」問題。如果系統的所有應用程式都是 TBI 感知或非 TBI 感知,而非兩者混合,安全性也可能會更高。
在使用者模式中移除標記
這會涉及在 syscall 層中移除所有標記,然後再將標記納入核心。這樣一來,您就不需要變更核心,而且核心可以不受標記影響。這項問題涉及使用者空間指標的錯誤處理。如果標記指標產生錯誤,則各個使用者空間處理常式會移除標記。
支援其他位址模式
這項提案應具備足夠的彈性,以便考量涉及「忽略」頂位元的其他硬體功能。我們不打算在近期支援這些功能,但應該已達到只要進行少量變更就能啟用這些功能的狀態。
ARM Memory Tagging Extension (MTE)
MTE 是 TBI 上層的功能,可找出記憶體存取錯誤。記憶體標記功能會將每個分配項目和指標與特定標記值建立關聯。如果指標的標記與其嘗試存取的配置不同,就表示標記不相符,導致記憶體存取錯誤。在 MTE 中,這個標記是 4 位元值,儲存在指標的頂層位元組中。
在這個 ABI 下,如果啟用 MTE,標記會參照指標的前 8 位元,但只有位元 56 至 59 會保留錯誤,因為硬體只保證會保留這 4 位元。
Intel 線性位址遮罩 (LAM)
LAM 是 x86 即將推出的功能,在載入/儲存時,會將指標中的前 7 位元或 16 位元遮罩。這項功能會透過變更 CR3 登錄器來全域控制。與 TBI 不同,LAM 不會保留任何頁面錯誤的標記位元。
既有技術與參考資料
AArch64 Linux 中的標記虛擬位址
這項提案的大部分設計靈感來自 Linux 的標記位址 ABI,也就是說,在接受標記指標時,大多數核心行為都不會受到影響。其中一個主要差異在於,Linux 支援針對每個執行緒切換 ABI,而這項提案旨在在建構/啟動時全域切換 ABI。此外,ARM TBI 會在 Linux 上隨時啟用,而 ARM TBI 也受同一個建構選項控制。