供應商 init

初始化程序幾乎沒有限制權限,並使用系統和供應商分區的輸入指令碼,在開機程序期間初始化系統。這項存取會在 Treble 系統/供應商分割中造成一個大漏洞,因為供應商指令碼可能會指示 init 存取不屬於穩定系統供應商應用程式二進位介面 (ABI) 的檔案、屬性等。

Vendor init 設計用具有安全性強化的 Linux (SELinux) 網域 vendor_init,以執行在 /vendor 中找到的指令 (具有供應商特定權限),藉此關閉這個漏洞。

機制

供應商使用 SELinux 結構定義 u:r:vendor_init:s0,在啟動程序初期啟動 init 的子程序。此 SELinux 內容的權限比預設初始化內容少得多,且其存取權僅限於特定廠商或穩定的系統廠商 ABI 的檔案、屬性等。

初始化會檢查載入的每個指令碼,查看其路徑是否以 /vendor 開頭,如果是的話,就會標記該指令碼,指出其指令必須在供應商初始化內容中執行。每個 init 內建項目都會以布林值加註,該布林值會指定指令是否必須在廠商初始化子程序中執行:

  • 大多數存取檔案系統的指令都會標註為在供應商初始化子程序中執行,因此會受到供應商初始化 SEPolicy 的管制。
  • 影響內部啟動狀態的大多數指令 (例如啟動和停止服務) 會在一般的 init 程序中執行。這些指令會知道供應商指令碼會呼叫它們,以便自行處理非 SELinux 權限。

init 的主要處理迴圈包含檢查,如果指令已註解要在供應商子程序中執行,且來自供應商指令碼,則該指令會透過程序間通訊 (IPC) 傳送至供應商初始子程序,該子程序會執行指令並將結果傳回至初始。

使用供應商初始化

供應商初始化功能預設為啟用,且其限制會套用至 /vendor 分區中的所有初始化指令碼。如果供應商的指令碼已不再存取系統專屬檔案、屬性等,供應商初始化應對供應商保持透明。

不過,如果特定供應商指令碼中的指令違反供應商初始化限制,則指令會失敗。失敗的指令會在內核記錄中顯示一行訊息 (可透過 dmesg 查看),表示初始化失敗。任何因 SELinux 政策而失敗的指令都會伴隨 SELinux 稽核作業。包含 SELinux 稽核的失敗範例:

type=1400 audit(1511821362.996:9): avc: denied { search } for pid=540 comm="init" name="nfc" dev="sda45" ino=1310721 scontext=u:r:vendor_init:s0 tcontext=u:object_r:nfc_data_file:s0 tclass=dir permissive=0
init: Command 'write /data/nfc/bad_file_access 1234' action=boot (/vendor/etc/init/hw/init.walleye.rc:422) took 2ms and failed: Unable to write to file '/data/nfc/bad_file_access': open() failed: Permission denied

如果指令失敗,您可以採取以下兩種做法:

  • 如果指令因預期限制而失敗 (例如指令存取系統檔案或屬性),則必須以 Treble 相容的方式重新實作指令,並只透過穩定的介面進行。永不允許規則可防止新增權限,以存取不屬於穩定版系統廠商 ABI 的系統檔案。
  • 如果 SELinux 標籤是新的,且尚未在系統 vendor_init.te 中授予權限,也未透過 neverallow 規則排除權限,則新標籤可能會在裝置專屬的 vendor_init.te 中授予權限。

如果是 Android 9 之前推出的裝置,您可以將 data_between_core_and_vendor_violators type 屬性新增至裝置專屬的 vendor_init.te 檔案,藉此略過 neverallows 規則。

程式碼位置

供應商初始化 IPC 的大部分邏輯位於 system/core/init/subcontext.cpp

指令表位於 system/core/init/builtins.cpp 中的 BuiltinFunctionMap 類別,並包含註解,指出指令是否必須在供應商初始子程序中執行。

供應商初始化程序的 SEPolicy 會分散在 system/sepolicy 中的私人 (system/sepolicy/private/vendor_init.te) 和公開 (system/sepolicy/public/vendor_init.te) 目錄。