RFC-0159:僅執行記憶體

RFC-0159:僅執行記憶體
狀態已接受
區域
  • 核心
  • 工具鏈
說明

支援對應僅執行記憶體。

問題
  • 89845
變更
作者
審查人員
提交日期 (年/月)2022-03-29
審查日期 (年/月)2022-05-10

摘要

本文件提議變更核心 API,以便支援具有僅限執行區隔的二進位檔,方法是在 zx_system_get_features 中新增功能檢查,並變更 launchpadprocess_builder 載入器,以及 Fuchsia 樹狀結構 libc 中的動態連結器來支援「--x」區隔。為在支援該功能的硬體上對應執行專用的頁面,制定了最終核心支援計畫。

載入執行檔後,我們通常不需要讀取可執行的記憶體。預設啟用僅限執行的程式碼,可提高 Fuchsia 使用者空間程序的安全性,並進一步採用最低權限的工程最佳做法。

提振精神

ARMv7m 中的 ARM MMU 已支援僅執行頁面,並允許對應記憶體頁面,這些頁面只能對應執行,且無法讀取或寫入。雖然可寫入的程式碼頁面已長期視為安全性威脅,但已證實可持續讀取程式碼,有助於應用程式公開在無謂的風險。具體來說,讀取程式碼頁面通常是攻擊鏈中的第一步,可防止程式碼讀取不肖人士。請參閱可讀取的程式碼安全性。此外,支援僅執行頁面非常適合 Fuchsia 的權限模型,且更符合最低權限原則:通常程式碼不需要讀取,只是可以執行。

相關人員

講師:

  • cpu@google.com

審查者:

  • phosek@google.com
  • mvanotti@google.com
  • maniscalco@google.com
  • travisg@google.com

背景

僅限執行的記憶體

僅執行記憶體 (XOM) 說明沒有讀取和寫入權限,且只能執行的記憶體頁面。ARMv7m 以上版本具備原生支援 XOM,不過在舊版 ISA 方面,有些需要注意的事項。詳情請參閱 XOM 和 PAN

本文件幾乎著重於 AArch64,但實作方式不受架構限制。當硬體和工具鍊為其他架構支援成熟的架構時,都能輕鬆運用 Fuchsia 中的僅執行支援。

程式碼頁面的權限

一開始,電腦支援直接存取實體記憶體,不會經過任何檢查或保護。推出 MMU 後,我們將程式的記憶體檢視畫面與基礎實體資源分離,藉此提供虛擬記憶體的關鍵抽象化機制。這可讓 OS 實作者透過程序抽象層,在程式之間提供強大的隔離機制,進而促進更靈活、安全且安全的程式設計模型。現今的 MMU 提供許多重要功能,例如分頁記憶體、快速位址轉譯和權限檢查。使用者也能透過頁面權限,大幅控管記憶體區域的存取和使用方式,這通常用於控制是否可以讀取、寫入或執行記憶體頁面。這是有關計畫安全性、錯誤隔離和安全性的重要屬性,因為程式會藉由硬體強制執行的權限檢查,限製程式濫用系統資源。

可寫入和執行檔的記憶體特別危險,因為可供攻擊者透過常見安全漏洞 (例如緩衝區溢位) 輕鬆執行任何程式碼。因此,許多 OS 設定明確禁止網頁同時是可寫入和執行 (W^X)。過去十幾年來,OpenBSD 開始在 2003 年透過 OpenBSD 3.3 openbsd-wxorx 新增對 W^X 的支援。另請參閱 SELinux W^X 政策 selinux-wxorx。可寫入程式碼對於在執行階段寫入記憶體的即時 (JIT) 編譯非常有用。我們可能會禁止使用 W|X 網頁,而 JIT 必須解決這個問題。其中一個簡單的方法是將程式碼寫入不可執行的網頁,然後再將網頁保護措施 (例如透過 mprotectzx_vmar_protect) 變更為可執行,但無法寫入 example-fuchsia-test。在幾乎所有情況下,W|X 的網頁都過於寬鬆。同樣地,可執行的網頁通常不需要讀取查看例外狀況。一般來說,允許讀取執行檔上的作業是不需要的,而且不應是預設值。

可讀取的程式碼

由於 ARM 的固定指示寬度具有大小限制,因此立即值具有大小限制。因此,載入作業會使用 PC-Relative 位址完成。為瞭解決這個問題,虛擬指示 ldr Rd, =imm 會在載入程式碼附近的常值集區中發出 imm。這與 XOM 不相容,因為這項工具會將資料儲存在必須閱讀的文字區段中。在程式碼集中搜尋使用常值集區,確保不會讀取可執行區隔時,我們發現 Zircon 中的某些 ldr Rd, =imm 用法已全部移除。Clang 不會針對 aarch64 使用常值集區,而是會發出多個指令以立即建立大型。Clang 有 -mexecute-only 旗標和別名 -mpure-code,但只有在 arm32 上才有意義,因為在指定 aarch64 時,這些標記固然是固有的。

範例:大型中級

這個範例說明 Clang 如何編譯這個 C 程式碼,以依據不同的目標 clang-example 組合成組合。第一列顯示 aarch64,底部顯示 arm32:

uint32_t a() {
    return 0x12345678u;
}
# -target aarch64
a:
    mov w0, #22136
    movk w0, #4660, lsl #16
    ret
# -target arm
a:
    ldr r0, .LCPI0_0
    bx lr
.LCPI0_0:
    .long 305419896

XOM 和 PAN

永不特殊權限存取權 (PAN) 是 ARM 晶片的安全性功能,可防止使用者透過核心模式存取使用者頁面的一般記憶體。此機制有助於防範潛在的核心安全漏洞,因為核心無法以正常的負載或商店指示接觸使用者記憶體。而,OS 需要關閉 PAN,或使用 ldtrsttr 指令存取這些頁面。目前還無法在 Fuchsia 中使用 PAN,但目前已有計劃支援 Zircon pan-fxb 支援這個 PAN。

Aarch64 頁面表格項目有 4 個相關位元,可控管頁面權限。2 位元用於使用者,且沒有權限執行。其餘兩個存取層級則用於描述這兩種存取層級的讀取和寫入頁面權限。僅在執行只能執行的對應作業會移除讀取和寫入權限,但允許使用者執行。

ARMv8 參考手冊中的表格僅使用 4 個可用位元,說明可能的記憶體保護措施。EL0 是使用者空間的例外狀況等級。第 0 和第 2 列說明如何建立使用者空間僅供執行的網頁。請參閱 ARMv8 參考手冊的 D5-34 階段 1。

使用者體驗 PXN AP[2:1] 取得更高例外狀況等級的存取權 從 EL0 存取
0 1 00 R、W X
0 1 01 R、W R、W、X
0 1 10 R X
0 1 11 R R、X

很遺憾,PAN 的演算法會判斷網頁是否不應開放存取,是否允許使用者讀取網頁。從 PAN 的角度來看,使用者執行專用頁面看起來就像特殊權限對應。這可讓核心在不應的情況下存取使用者記憶體,從而略過 PAN 的預期用途,使 PAN 和 XOM 與 XOM 不相容的平移問題。這將會導致日後使用 PAN 的行為不利於惡意運用核心觸碰使用者記憶體的攻擊,但偵測核心錯誤仍非常實用。

這個問題導致 Linux 和 Android 停止支援 XOM。Android 10 的 Android 11 在新增支援功能後會無限期支援,而且已將 Android 10 linux 還原的預設二進位檔設為預設值,這一點尤其明顯。他們計劃以硬體的形式重新啟用功能,讓解決問題的機會更加普及,但將加回時沒有具體的時間範圍。

ARM 開始提議採用「增強」PAN 或 ePAN 的解決方案,將 PAN 改為檢查使用者能否讀取的網頁,不是使用者執行檔。很遺憾,這類硬體可能不適用於任何以 Fuchsia 為目標的 Fuchsia 裝置。在 ePAN 建立 linux-re-land 之後,Linux 便重新加入其 XOM 實作項目。我們控制了裝置上的 ePAN 支援功能,而且與 PAN 和 XOM 不相容,不應封鎖核心導入 PAN。瞭解詳情

從圖 2 開始,沒有任何設定可以從核心移除讀取權限。唯一的例外狀況是 PAN,當核心嘗試輕觸使用者可理解的頁面時,可能會導致例外狀況。因此,我們無法為核心建立僅限執行的對應,因為核心無法將 EL1 中的頁面執行檔標示為無法讀取。因此,只能為使用者空間程序建立僅執行的對應。

指定 XOM 硬體

ELF 中的區隔權限會指出程式碼需要哪些權限才能正確執行。換句話說,如果軟體執行的裝置可以支援 XOM,軟體就不需要在建構時間知道該軟體是否支援 XOM。如果不需要讀取程式碼頁面,應無條件使用 XOM。至於 OS 和載入器,則會在系統允許 elf-segment-perm 的最大範圍內強制執行這些權限。

虛擬記憶體權限

POSIX 指定 mmap 可能會允許讀取 PROT_READ 尚未明確設定 posix-mmap 的網頁。x86 中的 Linux 和 macOS 和 M1 晶片的 macOS 在僅使用 PROT_EXEC 透過 mmap 要求頁面時不會失敗,而是改為將頁面設為 PROT_READ | PROT_EXEC。這些實作包含系統呼叫,最能有效滿足使用者的要求。另一方面, Fuchsia 系統呼叫一律明確明白其所能或無法遵循的範疇。zx_vmar_* 系統呼叫不會自動提報頁面的權限,例如其 POSIX 對應項目等頁面的權限。目前在沒有 ZX_VM_PERM_READ 的情況下要求頁面會一直失敗,因為硬體,且 OS 不支援沒有讀取權限的地圖頁面。如要支援具有僅執行片段和使用者空間程式 (會分配僅限執行記憶體的使用者空間程式) 的優雅轉換,必須在要求這些頁面之前,先檢查 OS 是否可對應僅執行的頁面。

可讀取的程式碼安全性

許多攻擊都需要透過閱讀程式碼頁面來找出程序相關資訊,才能找出「小工具」或感興趣的可執行程式碼。作業系統版面配置隨機化 (ASLR) 是一種技術,可讓作業系統在程序位址空間的半隨機位置載入二進位片段。由 Fuchsia 和許多其他 OS 使用,可遏阻需要知道程式碼或其他資料位於記憶體中的攻擊行為。將程式碼設為無法讀取,可進一步減少攻擊面。

程式碼重複使用攻擊 (如「return-to-libc」rtl-attack) 能將函式的控制回復到已知位址。libc 含有豐富功能對攻擊者來說非常實用,而且極可能與 libc 的連結,因此是相當邏輯選擇,我們會示範一般程式中提供的小工具是「完整導覽」,讓有心人士得以執行任意程式碼。

在許多情況下,競爭對手的目標是取得殼層。ASLR 可提高這類攻擊的難度,因為函式位址在程式叫用之間有差異。不過,ASLR 並非全面緩解的做法,因為攻擊者可以讀取程式碼頁面,透過檢查二進位檔中的位址,找出原本本來無法知道的函式。XOM 導致 ASLR 出現損毀的情況,攻擊者必須使用其他方式,找出特定程式碼頁面的位置相關資訊。

常見標記法

「rwx/r-x/–x」

這些代表 ELF 區隔的權限,這些區隔的權限會對應到具有對應權限的程序位址空間。這種標記通常用於描述檔案的權限,以及以 readelf 等工具為基礎的 ELF 區隔。r、w 和 x 均代表讀取、寫入和執行,「-」則代表未授予權限。僅限執行的區隔將擁有「--x」權限。

R^X、W|X 等...

如上所述,R、W 和 X 是指讀取、寫入和執行。「^」和「|」是類似 C 的 xor 和 C 等運算子。R^X 解讀為「讀取 xor 執行作業」。

「ax」

這個組合語法會將區段標示為已分配和執行檔。目前,連接器會將「ax」區段放入「r-x」的區隔中。lld 中的 --execute-only 標記會改為將這些區隔標示為「--x」。

設計

為了透過支援 XOM 強化使用者空間程式的安全性,我們的工具鍊和載入器都必須更新。Clang 驅動程式庫必須將「--execute-only」標記傳遞至連結器,確保「ax」區段會對應至「--x」區隔。此外,載入器也必須變更所有要求的權限是否至少包含讀取的完整性檢查,因為已經不再是如此。

由於只能在具有 ePAN 的硬體上使用 XOM,我們需要妥善支援轉換作業。我們提供兩種方案:

  1. 變更 vmar_* 函式,讓函式達到最佳效果,就像許多 mmap 實作一樣
  2. 建立在核心支援僅執行對應時查詢核心的方式,且在 XOM 無法使用時,載入器會將「--x」區隔的權限提升為「r-x」。
  3. 新增 ZX_VM_PERM_READ_IF_XOM_UNSUPPORTED 標記,供載入器與「--x」區隔搭配使用。

在任何情況下,都有可能會自動提升權限。第一種是最容易的方法,除了移除完整性檢查之外,載入器不需要進行任何變更。第二個選項比較複雜,其只需在載入器中新增一個簡單的檢查,然後再決定向 OS 要求哪些記憶體權限即可。第三個選項很實用,因為使用者程式碼較不容易出錯。

第一個選項最後會破壞 Fuchsia 目前嚴格合約,而使用者空間一律會明確指出系統呼叫可以或無法處理的項目。第 2 和 3 個選項在載入 ELF 檔案時也會產生模糊的記憶體權限。不過,這符合 ELF 規格。區隔權限不會指定分配給片段的記憶體 1:1 權限,而會指示記憶體至少具備哪些權限才能程式正確運作。ELF 載入器有權將「--x」區段對應至「r-x」記憶體「elf-segment-perm」

第一次破壞 Fuchsia 目前明確系統呼叫處理的合約,不是理想的做法。選項 2 和選項 3 都有值,而此 RFC 中建議的實作方式將以這兩個選項為基礎。

實作

系統呼叫附加項目

將新增旗標 ZX_VM_PERM_READ_IF_XOM_UNSUPPORTED,以發出各種 zx_vmar_* 系統呼叫,這些呼叫會採用 options 的權限標記,在不支援 XOM 時隱含新增讀取權限。在邏輯上,ZX_VM_PERM_READ_IF_XOM_UNSUPPORTED 只適用於 ZX_VM_PERM_EXEC,而非 ZX_VM_PERM_READ,但接受此標記的各種系統呼叫都不會將其視為不變。您可以放心將 ZX_VM_PERM_READ_IF_XOM_UNSUPPORTED 與任何其他標記組合搭配使用,在系統無法對應僅執行的網頁時,系統會將這個方法視為 ZX_VM_PERM_READ

系統將為 zx_system_get_features 新增 kindZX_FEATURE_KIND_VM,這樣就能產生與 ZX_FEATURE_KIND_CPU 類似的位元集。這個 API 也會推出新功能 ZX_VM_FEATURE_CAN_MAP_XOM。目前的實作一律會讓這個位元保持 false,因為 XOM 稍後才會啟用。載入器不會使用這個值,因為「r-x」記憶體權限對「--x」區段有效,但使用者空間還是必須讓使用者查詢這項功能。

系統載入器 ABI 變更

目前和未來的載入器會確保即使目標不支援 XOM,「--x」區隔還是可以載入記憶體中。載入僅限執行的區隔時,載入器會新增 ZX_VM_PERM_READ_IF_XOM_UNSUPPORTED

已傳送動態連接器 ABI 變更

同樣地,透過 SDK 隨附的 Fuchsia libc 中的動態連結器,在使用 ZX_VM_PERM_READ_IF_XOM_UNSUPPORTED 為「--x」區隔分配記憶體時,也會視需要提升權限。

編譯器工具鍊變更

指定 aarch64-*-fuchsia 時,clang 驅動程式庫也會變更為一律將 --execute-only 傳遞至連結器。此外,我們也需要選擇停用這項行為的方法,很有可能在連結器中新增「--no-execute-only」標記,以便程式輕鬆停用新的預設行為。

導入核心 XOM

硬體送達支援 ePAN 的硬體後,核心即可為記憶體頁面的要求只提供 ZX_VM_PERM_EXECUTE。實作 arm64 使用者文案可能需要更新,以確保與使用者記憶體存取權的限制一致。user_copy 應更新為使用 ldtrsttr 指令。這可確保使用者無法誘騙核心讀取他們無法讀取的頁面。此外,核心也會在幾個地方對對應進行假設,因此需要視情況變更。這項作業稍後就會進行。

不必要的變更

zx_process_read_memory 不需要變更,且在對僅執行的二進位檔進行偵錯時,偵錯工具應可正常運作。zx_process_read_memory 會忽略讀取來源網頁的權限,且只會檢查程序控點是否有 ZX_RIGHT_READZX_RIGHT_WRITE

zx_vmar_protect 會繼續照常運作。最重要的是,這表示程序可以在必要時透過讀取權限保護程式碼頁面。

效能

成效預計不會造成任何影響。

安全性

在此之前,在核心中導入具有「--x」區段的二進位檔之前,安全性和使用「r-x」區段的同等二進位檔一樣安全。硬體和 OS 同時支援 XOM 後,選擇使用僅限執行記憶體的程式將更加安全。請參閱「程式碼頁面權限」、「XOM 和 PAN」和「可讀取的程式碼安全性」等節。

隱私防護

除了安全性中提及的額外事項之外,您無須額外考量。

測試

當我們在核心中強制執行 XOM 支援時,zx_system_get_features 將會進行簡單的測試,也就是在建構期間預期系統傳回系統呼叫的項目。

如果 zx_system_get_features 回報頁面,且 OS 無法建立僅執行的網頁,ZX_VM_PERM_READ_IF_XOM_UNSUPPORTED 會測試其能否讀取頁面。

同樣地,elfload 程式庫也不會有任何實際測試,請儲存不會測試預期功能的模糊測試。而是會由其他仰賴它的元件測試其功能。請在此新增測試,確保「--x」區隔正確對應。process_builder 程式庫具備測試功能,可確保在 XOM 無法使用時,能正確要求可讀取及可執行的記憶體。

系統不會直接測試您對目前動態連結器所做的變更。我們計劃推出新的動態連結器,也會進行大量測試,包括測試「--x」區隔。

對 clang 驅動程式庫所做的變更,會在上游 LLVM 中進行測試。

我們也會設定測試設定,以在測試機器人上啟用 XOM,即使該硬體沒有 ePAN,我們也不會啟用 XOM。這有助於我們找出讀取程式碼網頁的樹狀結構程式,而必須選擇停用僅限執行的作業。

說明文件

將記錄 zx_system_get_features 的變更,以及使用 ZX_VM_FEATURE_CAN_MAP_XOM 種類查詢使用者空間的原因。同樣地,新的 ZX_VM_PERM_READ_IF_XOM_UNSUPPORTED 旗標也會記錄下來。對各種載入器和 clang 驅動程式庫預設值所做的變更不會記錄在這個 RFC 之外。

缺點、替代方案、未知

我們無法判定樹狀程式碼仰賴可讀取的可執行程式碼,並不容易。例如使用手寫組合文字中的資料常數、從其他工具鍊編譯的程式碼,或檢查程式的自我檢查。無論如何,需要可讀取程式碼頁面的程式仍會受益,因為其共用程式庫依附元件 (包括 libc) 只會標示為執行。將 Clang 工具鍊變更為預設值,改為僅執行的區隔,會導致依附可讀取程式碼的程式無法運作。目前,在建構期間要檢查程式是否仰賴這個行為,可能並不容易。然而,一旦發現程式需要「r-x」區隔,就能輕易停用預設的「--x」區隔。

對於需要讀取部分程式碼 (而非全部) 的程式,目前的工具無法輕易支援此功能。--execute-only linker 旗標會移除任何可執行片段的讀取權限,而且無法視需要將單一區段標示為需要讀取。如果程式希望出現此行為,就必須完全停用僅執行程式。

風險

clang 驅動程式庫預設為使用 --execute-only,以及從「--x」片段讀取的程式碼則可能在 XOM 地區硬體和核心支援而無法正常運作。這可能會對未變更的軟體造成潛在的前瞻相容性問題。雖然樹狀結構軟體中會進行測試,但很有可能不屬於樹狀結構中的程式碼。

既有圖片與參考資料

由於許多 POSIX 實作中會不明確處理 mmap 權限標記,因此不需要類比 zx_system_get_features(ZX_FEATURE_KIND_CAN_MAP_XOM, &feature)

Darwin 支援在較新款 Apple 晶片上使用 XOM,但使用專屬硬體功能,在實作上更加得心應手。其晶片提供硬體支援,可從核心和使用者記憶體中移除個別權限位元。這不會為 macOS 中的使用者空間啟用。apple-xom